Đơn nguyên bằng tiếng Anh đơn giản? (Đối với lập trình viên OOP không có nền tảng FP)

774
Noname 2010-04-25 03:42.

Theo thuật ngữ mà một lập trình viên OOP sẽ hiểu (không có bất kỳ nền tảng lập trình chức năng nào), đơn nguyên là gì?

Nó giải quyết vấn đề gì và những nơi phổ biến nhất mà nó được sử dụng là gì?

BIÊN TẬP:

Để làm rõ cách hiểu mà tôi đang tìm kiếm, giả sử bạn đang chuyển đổi một ứng dụng FP có monads thành một ứng dụng OOP. Bạn sẽ làm gì để chuyển trách nhiệm của các monads sang ứng dụng OOP?

19 answers

760
Eric Lippert 2010-04-25 04:28.

CẬP NHẬT: Câu hỏi này là chủ đề của một loạt blog dài vô cùng, bạn có thể đọc tại Monads - cảm ơn vì câu hỏi tuyệt vời!

Theo thuật ngữ mà một lập trình viên OOP sẽ hiểu (không có bất kỳ nền tảng lập trình chức năng nào), đơn nguyên là gì?

Một đơn nguyên là một "khuếch đại" của các loạituân theo quy tắc nhất địnhtrong đó có một số hoạt động cung cấp .

Đầu tiên, một "bộ khuếch đại của các loại" là gì? Ý tôi là một số hệ thống cho phép bạn chọn một loại và biến nó thành một loại đặc biệt hơn. Ví dụ, trong C # hãy xem xét Nullable<T>. Đây là một bộ khuếch đại của các loại. Nó cho phép bạn chọn một loại, giả sử intvà thêm một khả năng mới vào loại đó, cụ thể là bây giờ nó có thể là rỗng khi trước đây không thể.

Ví dụ thứ hai, hãy xem xét IEnumerable<T>. Nó là một bộ khuếch đại của các loại. Nó cho phép bạn lấy một kiểu, chẳng hạn, stringvà thêm một khả năng mới cho kiểu đó, cụ thể là bây giờ bạn có thể tạo một chuỗi chuỗi từ bất kỳ số chuỗi đơn nào.

"Các quy tắc nhất định" là gì? Tóm lại, có một cách hợp lý để các chức năng trên kiểu cơ bản hoạt động trên kiểu khuếch đại sao cho chúng tuân theo các quy tắc bình thường của thành phần chức năng. Ví dụ: nếu bạn có một hàm trên số nguyên, hãy nói

int M(int x) { return x + N(x * 2); }

thì chức năng tương ứng trên Nullable<int>có thể làm cho tất cả các nhà khai thác và cuộc gọi trong đó hoạt động cùng nhau "theo cùng một cách" mà họ đã làm trước đó.

(Điều đó cực kỳ mơ hồ và không chính xác; bạn đã yêu cầu một lời giải thích mà không có bất cứ điều gì về kiến ​​thức về thành phần chức năng.)

Các "hoạt động" là gì?

  1. Có một hoạt động "đơn vị" (đôi khi được gọi một cách khó hiểu là hoạt động "trả về") nhận một giá trị từ một kiểu đơn giản và tạo ra giá trị đơn nguyên tương đương. Về bản chất, điều này cung cấp một cách để lấy một giá trị của kiểu không được khuếch đại và biến nó thành giá trị của kiểu được khuếch đại. Nó có thể được triển khai như một hàm tạo trong ngôn ngữ OO.

  2. Có một hoạt động "ràng buộc" nhận một giá trị đơn nguyên và một hàm có thể chuyển đổi giá trị và trả về một giá trị đơn nguyên mới. Bind là thao tác chính xác định ngữ nghĩa của đơn nguyên. Nó cho phép chúng ta chuyển đổi các hoạt động trên kiểu không được chuẩn hóa thành các thao tác trên kiểu được khuếch đại, tuân theo các quy tắc của thành phần chức năng đã đề cập trước đó.

  3. Thường có một cách để lấy lại loại không được chỉnh sửa ra khỏi loại khuếch đại. Nói chính xác thì thao tác này không bắt buộc phải có đơn nguyên. (Mặc dù điều đó là cần thiết nếu bạn muốn có một điểm chung . Chúng tôi sẽ không xem xét thêm những điều đó trong bài viết này.)

Một lần nữa, hãy lấy Nullable<T>làm ví dụ. Bạn có thể biến một intthành a Nullable<int>với hàm tạo. Trình biên dịch C # sẽ xử lý hầu hết các "nâng" có thể null cho bạn, nhưng nếu không, quá trình chuyển đổi nâng rất đơn giản: một hoạt động, chẳng hạn như,

int M(int x) { whatever }

được chuyển thành

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

Và biến một Nullable<int>trở lại thành một intđược thực hiện với Valuetài sản.

Đó là sự chuyển đổi chức năng là bit quan trọng. Lưu ý cách ngữ nghĩa thực tế của phép toán nullable - mà một phép toán trên một nulltruyền dẫn null- được ghi lại trong phép biến đổi. Chúng ta có thể khái quát điều này.

Giả sử bạn có một hàm từ intđến int, giống như hàm ban đầu của chúng tôi M. Bạn có thể dễ dàng biến điều đó thành một hàm nhận intvà trả về a Nullable<int>vì bạn chỉ có thể chạy kết quả thông qua hàm tạo nullable. Bây giờ, giả sử bạn có phương pháp bậc cao này:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Hãy xem bạn có thể làm gì với điều đó? Bất kỳ phương thức nào nhận một intvà trả về một int, hoặc lấy một intvà trả về một Nullable<int>bây giờ có thể có ngữ nghĩa nullable được áp dụng cho nó .

Hơn nữa: giả sử bạn có hai phương pháp

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

và bạn muốn soạn chúng:

Nullable<int> Z(int s) { return X(Y(s)); }

Đó là, Zlà thành phần của XY. Nhưng bạn không thể làm điều đó vì Xnhận một intYtrả về a Nullable<int>. Nhưng vì bạn có thao tác "ràng buộc", bạn có thể làm cho việc này hoạt động:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

Hoạt động liên kết trên một đơn nguyên là điều làm cho thành phần của các chức năng trên các loại khuếch đại hoạt động. Các "quy tắc" mà tôi đã truyền tay ở trên là đơn nguyên bảo tồn các quy tắc của thành phần chức năng bình thường; rằng sáng tác với các chức năng nhận dạng dẫn đến chức năng ban đầu, sáng tác đó là liên kết, v.v.

Trong C #, "Bind" được gọi là "SelectMany". Hãy xem cách nó hoạt động trên đơn nguyên trình tự. Chúng ta cần có hai thứ: biến một giá trị thành một chuỗi và ràng buộc các hoạt động trên chuỗi. Như một phần thưởng, chúng tôi cũng có "biến một chuỗi trở lại thành một giá trị". Các hoạt động đó là:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

Quy tắc đơn nguyên nullable là "kết hợp hai hàm tạo ra nullable với nhau, kiểm tra xem hàm bên trong có kết quả là null hay không; nếu có, tạo ra null, nếu không, thì gọi hàm bên ngoài với kết quả". Đó là ngữ nghĩa mong muốn của nullable.

Quy tắc đơn nguyên trình tự là "kết hợp hai hàm tạo ra chuỗi với nhau, áp dụng chức năng bên ngoài cho mọi phần tử được tạo ra bởi hàm bên trong, sau đó nối tất cả các chuỗi kết quả với nhau". Các ngữ nghĩa cơ bản của monads được nắm bắt trong Bind/ SelectManymethod; đây là phương pháp cho bạn biết đơn nguyên thực sự có ý nghĩa gì .

Chúng tôi có thể làm tốt hơn nữa. Giả sử bạn có một chuỗi các int và một phương thức nhận int và kết quả là chuỗi các chuỗi. Chúng tôi có thể tổng quát hóa hoạt động liên kết để cho phép thành phần các hàm nhận và trả về các loại khuếch đại khác nhau, miễn là đầu vào của một đầu vào khớp với đầu ra của cái kia:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

Vì vậy, bây giờ chúng ta có thể nói "khuếch đại chùm số nguyên riêng lẻ này thành một chuỗi số nguyên. Biến đổi số nguyên cụ thể này thành một chùm chuỗi, được khuếch đại thành một chuỗi chuỗi. Bây giờ hãy đặt cả hai phép toán với nhau: khuếch đại chùm số nguyên này thành nối của tất cả các trình tự của chuỗi. " Đơn nguyên cho phép bạn soạn các bản khuếch đại của mình.

Nó giải quyết vấn đề gì và những nơi phổ biến nhất mà nó được sử dụng là gì?

Điều đó giống như việc hỏi "mô hình singleton giải quyết những vấn đề gì?", Nhưng tôi sẽ đưa ra câu hỏi.

Các đơn nguyên thường được sử dụng để giải quyết các vấn đề như:

  • Tôi cần tạo các khả năng mới cho loại này và vẫn kết hợp các chức năng cũ trên loại này để sử dụng các khả năng mới.
  • Tôi cần nắm bắt một loạt các thao tác trên các loại và biểu diễn các thao tác đó dưới dạng các đối tượng có thể kết hợp, tạo ra các bố cục lớn hơn và lớn hơn cho đến khi tôi có được một loạt các thao tác phù hợp và sau đó tôi cần bắt đầu thu được kết quả.
  • Tôi cần trình bày rõ ràng các hoạt động gây tác dụng phụ bằng ngôn ngữ ghét tác dụng phụ

C # sử dụng monads trong thiết kế của nó. Như đã đề cập, mẫu nullable rất giống với "có thể là đơn nguyên". LINQ được xây dựng hoàn toàn từ monads; các SelectManyphương pháp là những gì hiện các công việc ngữ nghĩa của thành phần hoạt động. (Erik Meijer thích chỉ ra rằng mọi chức năng LINQ thực sự có thể được thực hiện bởi SelectMany; mọi thứ khác chỉ là một sự tiện lợi.)

Để làm rõ cách hiểu mà tôi đang tìm kiếm, giả sử bạn đang chuyển đổi một ứng dụng FP có monads thành một ứng dụng OOP. Bạn sẽ làm gì để chuyển trách nhiệm của các monads vào ứng dụng OOP?

Hầu hết các ngôn ngữ OOP không có một hệ thống kiểu đủ phong phú để thể hiện trực tiếp mẫu đơn nguyên; bạn cần một hệ thống kiểu hỗ trợ các kiểu cao hơn kiểu chung. Vì vậy, tôi sẽ không cố gắng làm điều đó. Thay vào đó, tôi sẽ triển khai các kiểu chung đại diện cho từng đơn nguyên và triển khai các phương thức đại diện cho ba hoạt động bạn cần: biến một giá trị thành một giá trị khuếch đại, (có thể) biến một giá trị được khuếch đại thành một giá trị và chuyển một hàm trên các giá trị không được khuếch đại thành một hàm trên các giá trị được khuếch đại.

Một nơi tốt để bắt đầu là cách chúng tôi triển khai LINQ trong C #. Nghiên cứu SelectManyphương pháp; nó là chìa khóa để hiểu cách thức hoạt động của đơn nguyên trình tự trong C #. Nó là một phương pháp rất đơn giản, nhưng rất mạnh mẽ!


Đề xuất, đọc thêm:

  1. Để có giải thích lý thuyết và sâu hơn về các monads trong C #, tôi thực sự giới thiệu bài viết của đồng nghiệp ( Eric Lippert ) Wes Dyer về chủ đề này. Bài viết này là những gì các monads đã giải thích cho tôi khi cuối cùng họ đã "nhấp vào" cho tôi.
    • The Marvels of Monads
  2. Một minh họa tốt về lý do tại sao bạn có thể muốn có một đơn nguyên xung quanh (sử dụng Haskell trong các ví dụ của nó) .
    • Bạn có thể đã phát minh ra Monads! (Và có thể bạn đã có.) Bởi Dan Piponi
  3. Sắp xếp, "bản dịch" của bài viết trước sang JavaScript.
    • Bản dịch từ Haskell sang JavaScript của các phần được chọn trong phần giới thiệu hay nhất về monads mà tôi từng đọc bởi James Coglan

363
cibercitizen1 2015-01-26 00:42.

Tại sao chúng ta cần monads?

  1. Chúng tôi chỉ muốn lập trình bằng các hàm . ("lập trình chức năng" sau tất cả -FP).
  2. Sau đó, chúng tôi có một vấn đề lớn đầu tiên. Đây là một chương trình:

    f(x) = 2 * x

    g(x,y) = x / y

    Làm thế nào chúng ta có thể nói những gì sẽ được thực hiện đầu tiên ? Làm thế nào chúng ta có thể hình thành một chuỗi các hàm có thứ tự (tức là một chương trình ) chỉ sử dụng nhiều hơn các hàm?

    Giải pháp: soạn các hàm . Nếu bạn muốn trước gvà sau đó f, chỉ cần viết f(g(x,y)). Được rồi nhưng ...

  3. Các vấn đề khác: một số chức năng có thể bị lỗi (tức là g(2,0)chia cho 0). Chúng tôi không có "ngoại lệ" trong FP . Làm thế nào để chúng tôi giải quyết nó?

    Giải pháp: Hãy cho phép các hàm trả về hai loại thứ : thay vì có g : Real,Real -> Real(hàm từ hai thực thành một thực), hãy cho phép g : Real,Real -> Real | Nothing(hàm từ hai thực thành (thực hoặc không)).

  4. Nhưng các hàm nên (đơn giản hơn) chỉ trả về một thứ .

    Giải pháp: hãy tạo một loại dữ liệu mới để trả về, một " kiểu quyền anh " bao gồm có thể là thực hoặc đơn giản là không có gì. Do đó, chúng ta có thể có g : Real,Real -> Maybe Real. Được rồi nhưng ...

  5. Điều gì xảy ra bây giờ f(g(x,y))? fchưa sẵn sàng để tiêu thụ a Maybe Real. Và, chúng tôi không muốn thay đổi mọi chức năng mà chúng tôi có thể kết nối với gđể sử dụng a Maybe Real.

    Giải pháp: hãy có một chức năng đặc biệt để "kết nối" / "soạn" / "liên kết" . Bằng cách đó, chúng ta có thể điều chỉnh đầu ra của một hàm để cung cấp cho hàm sau.

    Trong trường hợp của chúng tôi: g >>= f(kết nối / soạn thư gtới f). Chúng tôi muốn >>=lấy gđầu ra của, kiểm tra nó và, trong trường hợp nó Nothingchỉ là không gọi fvà quay lại Nothing; hoặc ngược lại, trích xuất hộp Realvà cho ăn fcùng với nó. (Thuật toán này chỉ thực hiện >>=đối với các Maybeloại).

  6. Nhiều vấn đề khác nảy sinh có thể được giải quyết bằng cách sử dụng cùng một mẫu này: 1. Sử dụng một "hộp" để mã hóa / lưu trữ các ý nghĩa / giá trị khác nhau và có các chức năng như gvậy trả về các "giá trị được đóng hộp" đó. 2. Nhờ các nhà soạn nhạc / liên kết g >>= fgiúp kết nối gđầu ra fcủa với đầu vào của, vì vậy chúng tôi không phải thay đổi fgì cả.

  7. Các vấn đề đáng chú ý có thể được giải quyết bằng kỹ thuật này là:

    • có trạng thái toàn cục mà mọi chức năng trong chuỗi chức năng ("chương trình") có thể chia sẻ: giải pháp StateMonad.

    • Chúng tôi không thích "hàm không tinh khiết": các hàm mang lại đầu ra khác nhau cho cùng một đầu vào. Do đó, hãy đánh dấu các hàm đó, làm cho chúng trả về giá trị được gắn thẻ / đóng hộp: IOmonad.

Hạnh phúc trọn vẹn !!!!

87
JacquesB 2010-05-20 07:58.

Tôi muốn nói sự tương tự OO gần nhất với monads là " mẫu lệnh ".

Trong mẫu lệnh, bạn bọc một câu lệnh hoặc biểu thức thông thường trong một đối tượng lệnh . Đối tượng lệnh phơi bày một thực hiện phương pháp đó thực hiện báo cáo kết quả bọc. Vì vậy, câu lệnh được chuyển thành các đối tượng lớp đầu tiên có thể được truyền xung quanh và thực thi theo ý muốn. Các lệnh có thể được soạn để bạn có thể tạo một đối tượng chương trình bằng cách xâu chuỗi và lồng các đối tượng lệnh.

Các lệnh được thực hiện bởi một đối tượng riêng biệt, người gọi . Lợi ích của việc sử dụng mẫu lệnh (thay vì chỉ thực hiện một loạt các câu lệnh thông thường) là những kẻ xâm lược khác nhau có thể áp dụng logic khác nhau để thực hiện các lệnh như thế nào.

Mẫu lệnh có thể được sử dụng để thêm (hoặc xóa) các tính năng ngôn ngữ không được ngôn ngữ chủ hỗ trợ. Ví dụ: trong một ngôn ngữ OO giả định không có ngoại lệ, bạn có thể thêm ngữ nghĩa ngoại lệ bằng cách hiển thị các phương thức "thử" và "ném" cho các lệnh. Khi một lệnh gọi ném, kẻ gọi ngược lại qua danh sách (hoặc cây) lệnh cho đến lần gọi "thử" cuối cùng. Ngược lại, bạn có thể loại bỏ ngữ nghĩa ngoại lệ khỏi một ngôn ngữ (nếu bạn cho rằng ngoại lệ là không tốt ) bằng cách bắt tất cả các ngoại lệ được ném ra bởi từng lệnh riêng lẻ và chuyển chúng thành mã lỗi sau đó được chuyển cho lệnh tiếp theo.

Thậm chí những ngữ nghĩa thực thi ưa thích hơn như giao dịch, thực thi không xác định hoặc liên tục có thể được triển khai như thế này bằng một ngôn ngữ vốn không hỗ trợ nó. Đó là một mô hình khá mạnh mẽ nếu bạn nghĩ về nó.

Trong thực tế, các mẫu lệnh không được sử dụng như một tính năng ngôn ngữ chung như thế này. Chi phí chuyển mỗi câu lệnh thành một lớp riêng biệt sẽ dẫn đến lượng mã soạn sẵn không thể chịu nổi. Nhưng về nguyên tắc, nó có thể được sử dụng để giải quyết các vấn đề tương tự như monads được sử dụng để giải quyết trong fp.

63
BMeph 2010-07-17 14:14.

Theo thuật ngữ mà một lập trình viên OOP sẽ hiểu (không có bất kỳ nền tảng lập trình chức năng nào), đơn nguyên là gì?

Nó giải quyết vấn đề gì và những nơi phổ biến nhất mà nó được sử dụng là gì? Những nơi phổ biến nhất mà nó được sử dụng?

Về mặt lập trình OO, đơn nguyên là một giao diện (hoặc nhiều khả năng là một mixin), được tham số hóa bởi một kiểu, với hai phương thức returnbindmô tả:

  • Cách đưa một giá trị vào để nhận một giá trị đơn nguyên của loại giá trị được đưa vào đó;
  • Cách sử dụng một hàm tạo giá trị đơn nguyên từ một hàm không đơn nguyên, trên giá trị đơn nguyên.

Vấn đề mà nó giải quyết là cùng một loại vấn đề mà bạn mong đợi từ bất kỳ giao diện nào, cụ thể là, "Tôi có một loạt các lớp khác nhau làm những việc khác nhau, nhưng dường như làm những việc khác nhau đó theo cách có điểm giống nhau cơ bản. Làm thế nào tôi có thể mô tả sự giống nhau đó giữa chúng không, ngay cả khi bản thân các lớp không thực sự là kiểu con của bất cứ thứ gì gần hơn với chính lớp 'Đối tượng'? "

Cụ thể hơn, Monad"giao diện" tương tự IEnumeratorhoặc IIteratorở chỗ nó nhận một loại mà chính nó nhận một loại. MonadMặc dù vậy, "điểm" chính là có thể kết nối các hoạt động dựa trên kiểu nội thất, thậm chí đến mức có "kiểu bên trong" mới, trong khi vẫn giữ - hoặc thậm chí nâng cao - cấu trúc thông tin của lớp chính.

42
VonC 2010-07-18 12:39.

Bạn có một bài thuyết trình gần đây " Monadologie - trợ giúp chuyên nghiệp về loại lo âu " của Christopher League (ngày 12 tháng 7 năm 2010), khá thú vị về các chủ đề tiếp diễn và đơn nguyên.
Video đi kèm với bản trình bày (chia sẻ slide) này thực sự có sẵn tại vimeo .
Phần Monad bắt đầu trong khoảng 37 phút, trong video dài một giờ này và bắt đầu với trang trình bày 42 trong 58 bản trình bày trang trình bày của nó.

Nó được trình bày là "mẫu thiết kế hàng đầu cho lập trình chức năng", nhưng ngôn ngữ được sử dụng trong các ví dụ là Scala, cả OOP và chức năng.
Bạn có thể đọc thêm về Monad trong Scala trong bài đăng trên blog " Monads - Một cách khác để tính toán trừu tượng trong Scala ", từ Debasish Ghosh (27 tháng 3 năm 2008).

Một phương thức khởi tạo kiểu M là một đơn nguyên nếu nó hỗ trợ các hoạt động sau:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

Vì vậy, ví dụ (trong Scala):

  • Option là một đơn nguyên
    đơn vị def [A] (x: A): Option [A] = Some (x)

    def flatMap [A, B] (m: Option [A]) (f: A => Option [B]): Option [B] =
      tôi phù hợp với {
       trường hợp Không có => Không có
       case Some (x) => f (x)
      }
  • List là đơn nguyên
    đơn vị def [A] (x: A): Danh sách [A] = Danh sách (x)

    def flatMap [A, B] (m: List [A]) (f: A => List [B]): List [B] =
      tôi phù hợp với {
        case Nil => Nil
        trường hợp x :: xs => f (x) ::: flatMap (xs) (f)
      }

Monad là một vấn đề lớn trong Scala vì cú pháp thuận tiện được xây dựng để tận dụng các cấu trúc Monad:

forhiểu trong Scala :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

được dịch bởi trình biên dịch thành:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

Sự trừu tượng quan trọng là flatMap, liên kết tính toán thông qua chuỗi.
Mỗi lệnh gọi flatMaptrả về cùng một kiểu cấu trúc dữ liệu (nhưng có giá trị khác nhau), dùng làm đầu vào cho lệnh tiếp theo trong chuỗi.

Trong đoạn mã trên, flatMap nhận đầu vào là một bao đóng (SomeType) => List[AnotherType]và trả về a List[AnotherType]. Điểm quan trọng cần lưu ý là tất cả các bản đồ phẳng đều có cùng kiểu đóng làm đầu vào và trả về cùng kiểu làm đầu ra.

Đây là thứ "ràng buộc" chuỗi tính toán - mọi mục của chuỗi trong phần cho-hiểu phải tuân theo ràng buộc cùng loại này.


Nếu bạn thực hiện hai thao tác (có thể thất bại) và chuyển kết quả cho thao tác thứ ba, như:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

nhưng nếu không tận dụng Monad, bạn sẽ nhận được mã OOP phức tạp như:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

trong khi với Monad, bạn có thể làm việc với các kiểu thực tế ( Venue, User) như tất cả các thao tác hoạt động và giữ ẩn nội dung xác minh Tùy chọn, tất cả là do bản đồ phẳng của cú pháp for:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

Phần lợi nhuận sẽ chỉ được thực hiện nếu cả ba hàm đều có Some[X]; bất kỳ Nonesẽ được trả lại trực tiếp confirm.


Vì thế:

Các đơn nguyên cho phép tính toán theo thứ tự trong Lập trình chức năng, cho phép chúng ta lập mô hình trình tự các hành động ở dạng có cấu trúc đẹp, giống như DSL.

Và sức mạnh lớn nhất đi kèm với khả năng soạn các monads phục vụ các mục đích khác nhau, thành các bản trừu tượng có thể mở rộng trong một ứng dụng.

Việc sắp xếp và xâu chuỗi các hành động của một đơn nguyên này được thực hiện bởi trình biên dịch ngôn ngữ thực hiện việc chuyển đổi thông qua phép thuật của các bao đóng.


Nhân tiện, Monad không chỉ là mô hình tính toán được sử dụng trong FP:

Lý thuyết phạm trù đề xuất nhiều mô hình tính toán. Trong số họ

  • mô hình tính toán Mũi tên
  • mô hình tính toán đơn nguyên
  • Mô hình tính toán ứng dụng
39
Dmitri Zaitsev 2016-05-21 01:17.

Để tôn trọng những người đọc nhanh, trước tiên tôi bắt đầu với định nghĩa chính xác, tiếp tục với giải thích "tiếng Anh đơn giản" nhanh hơn, và sau đó chuyển sang các ví dụ.

Đây là một định nghĩa ngắn gọn và chính xác được sửa lại một chút:

Một đơn nguyên (khoa học máy tính) là chính thức một bản đồ mà:

  • gửi mọi kiểu Xcủa một số ngôn ngữ lập trình nhất định đến một kiểu mới T(X)(được gọi là "kiểu tính Ttoán có giá trị trong X");

  • được trang bị quy tắc soạn hai hàm của biểu mẫu f:X->T(Y)g:Y->T(Z)hàm g∘f:X->T(Z);

  • theo cách liên kết theo nghĩa hiển nhiên và đơn nguyên đối với một hàm đơn vị nhất định được gọi pure_X:X->T(X), được coi là lấy một giá trị cho phép tính thuần túy chỉ trả về giá trị đó.

Vì vậy, nói một cách đơn giản, đơn nguyênquy tắc để chuyển từ bất kỳ kiểu nào Xsang kiểu khácT(X)quy tắc để chuyển từ hai hàm f:X->T(Y)g:Y->T(Z)(mà bạn muốn soạn nhưng không thể) sang một hàm mớih:X->T(Z) . Tuy nhiên, điều này không phải là thành phần theo nghĩa toán học chặt chẽ. Về cơ bản chúng ta đang "bẻ cong" thành phần của hàm hoặc xác định lại cách các hàm được cấu tạo.

Thêm vào đó, chúng tôi yêu cầu quy tắc sáng tác của đơn nguyên phải thỏa mãn các tiên đề toán học "hiển nhiên":

  • Tính liên kết : Soạn fvới gvà sau đó với h(từ bên ngoài) cũng giống như soạn gvới hvà sau đó với f(từ bên trong).
  • Thuộc tính Unital : Soạn fvới chức năng nhận dạng ở hai bên sẽ mang lại hiệu quả f.

Một lần nữa, nói một cách dễ hiểu, chúng ta không thể cứ điên cuồng xác định lại thành phần chức năng của mình theo ý muốn:

  • Đầu tiên chúng ta cần sự liên kết để có thể soạn một số hàm liên tiếp f(g(h(k(x))), ví dụ , và không phải lo lắng về việc chỉ định các cặp hàm soạn thứ tự. Vì quy tắc đơn nguyên chỉ quy định cách tạo một cặp hàm , nên nếu không có tiên đề đó, chúng ta sẽ cần biết cặp nào được tạo trước và cứ tiếp tục như vậy. (Lưu ý rằng khác với tính chất giao hoán được fcấu tạo với gcũng giống như thuộc tính gcấu tạo với f, không bắt buộc).
  • Và thứ hai, chúng ta cần thuộc tính unital, nói một cách đơn giản rằng danh tính sáng tạo một cách tầm thường theo cách chúng ta mong đợi. Vì vậy, chúng tôi có thể cấu trúc lại các chức năng một cách an toàn bất cứ khi nào các danh tính đó có thể được trích xuất.

Tóm lại, một lần nữa: Đơn nguyên là quy tắc mở rộng kiểu và các hàm hợp thành thỏa mãn hai tiên đề - tính kết hợp và tính chất đơn nguyên.

Về mặt thực tế, bạn muốn đơn nguyên được thực hiện cho bạn bằng ngôn ngữ, trình biên dịch hoặc khuôn khổ sẽ đảm nhận các chức năng soạn thảo cho bạn. Vì vậy, bạn có thể tập trung vào việc viết logic của hàm của bạn hơn là lo lắng cách thực thi của chúng được thực hiện.

Tóm lại, về cơ bản là nó.


Là một nhà toán học chuyên nghiệp, tôi muốn tránh gọi h"thành phần" của fg. Bởi vì về mặt toán học, nó không phải vậy. Gọi nó là "thành phần" không chính xác cho rằng đó hlà thành phần toán học thực sự, mà nó không phải là. Nó thậm chí không được xác định duy nhất bởi fg. Thay vào đó, nó là kết quả của "quy tắc soạn thảo" các chức năng mới của đơn nguyên chúng tôi. Cái nào có thể hoàn toàn khác với thành phần toán học thực tế ngay cả khi cái sau tồn tại!


Để làm cho nó bớt khô khan, hãy để tôi cố gắng minh họa nó bằng ví dụ mà tôi đang chú thích bằng các phần nhỏ, vì vậy bạn có thể bỏ qua ngay vấn đề.

Ném ngoại lệ làm ví dụ Monad

Giả sử chúng ta muốn soạn hai hàm:

f: x -> 1 / x
g: y -> 2 * y

Nhưng f(0)không được xác định, vì vậy một ngoại lệ eđược ném ra. Sau đó, làm thế nào bạn có thể xác định giá trị tổng hợp g(f(0))? Tất nhiên lại ném ra một ngoại lệ! Có thể giống nhau e. Có thể là một ngoại lệ mới được cập nhật e1.

Điều gì chính xác xảy ra ở đây? Đầu tiên, chúng ta cần (các) giá trị ngoại lệ mới (khác hoặc giống nhau). Bạn có thể gọi chúng nothinghoặc nullhoặc bất cứ điều gì nhưng bản chất vẫn giữ nguyên - chúng phải là các giá trị mới, ví dụ như nó không phải là một numbertrong ví dụ của chúng tôi ở đây. Tôi không muốn gọi chúng nullđể tránh nhầm lẫn với cách nullcó thể được triển khai bằng bất kỳ ngôn ngữ cụ thể nào. Tương tự, tôi thích tránh nothingvì nó thường gắn liền với null, về nguyên tắc, là điều nullnên làm, tuy nhiên, nguyên tắc đó thường bị bẻ cong vì bất kỳ lý do thực tế nào.

Chính xác thì ngoại lệ là gì?

Đây là một vấn đề nhỏ đối với bất kỳ lập trình viên có kinh nghiệm nào nhưng tôi muốn bỏ vài từ chỉ để dập tắt bất kỳ sự nhầm lẫn nào:

Ngoại lệ là một đối tượng đóng gói thông tin về kết quả thực thi không hợp lệ xảy ra như thế nào.

Điều này có thể bao gồm việc loại bỏ bất kỳ chi tiết nào và trả về một giá trị chung duy nhất (như NaNhoặc null) hoặc tạo một danh sách nhật ký dài hoặc chính xác những gì đã xảy ra, gửi nó đến cơ sở dữ liệu và sao chép tất cả trên lớp lưu trữ dữ liệu phân tán;)

Sự khác biệt quan trọng giữa hai ví dụ ngoại lệ cực đoan này là trong trường hợp đầu tiên không có tác dụng phụ . Trong thứ hai có. Điều này đưa chúng ta đến câu hỏi (nghìn đô la):

Các ngoại lệ có được phép trong các hàm thuần túy không?

Câu trả lời ngắn gọn hơn : Có, nhưng chỉ khi chúng không dẫn đến tác dụng phụ.

Câu trả lời dài hơn. Để thuần túy, đầu ra của hàm của bạn phải được xác định duy nhất bởi đầu vào của nó. Vì vậy, chúng tôi sửa đổi hàm fcủa mình bằng cách gửi 0đến giá trị trừu tượng mới emà chúng tôi gọi là ngoại lệ. Chúng tôi đảm bảo rằng giá trị đó ekhông chứa thông tin bên ngoài không được xác định duy nhất bởi thông tin đầu vào của chúng tôi x. Vì vậy, đây là một ví dụ về ngoại lệ mà không có tác dụng phụ:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

Và đây là một với tác dụng phụ:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

Trên thực tế, nó chỉ có tác dụng phụ nếu thông điệp đó có thể thay đổi trong tương lai. Nhưng nếu nó được đảm bảo không bao giờ thay đổi, thì giá trị đó sẽ trở nên có thể dự đoán được duy nhất và do đó không có tác dụng phụ.

Để làm cho nó thậm chí còn buồn hơn. Một hàm trả về 42bao giờ hết rõ ràng là thuần túy. Nhưng nếu ai đó điên rồ quyết định tạo 42một biến mà giá trị có thể thay đổi, thì chính hàm tương tự sẽ không còn thuần túy trong các điều kiện mới.

Lưu ý rằng tôi đang sử dụng ký hiệu chữ đối tượng để đơn giản hóa bản chất. Thật không may, mọi thứ đang lộn xộn trong các ngôn ngữ như JavaScript, nơi errorkhông phải là một loại hoạt động theo cách chúng ta muốn ở đây đối với thành phần hàm, trong khi các loại thực tế thích nullhoặc NaNkhông hoạt động theo cách này mà là đi qua một số nhân tạo và không phải lúc nào cũng trực quan loại chuyển đổi.

Nhập phần mở rộng

Khi chúng tôi muốn thay đổi thông báo bên trong ngoại lệ của mình, chúng tôi thực sự đang khai báo một kiểu mới Echo toàn bộ đối tượng ngoại lệ và sau đó Đó là những gì maybe numberhiện, ngoài tên khó hiểu của nó, là loại numberhoặc thuộc loại ngoại lệ mới E, vì vậy nó thực sự là sự kết hợp number | Ecủa numberE. Đặc biệt, nó phụ thuộc vào cách chúng ta muốn xây dựng E, điều này không được gợi ý hay phản ánh trong tên maybe number.

Thành phần chức năng là gì?

Đây là toán học chức năng hoạt động lấy f: X -> Yg: Y -> Zvà xây dựng thành phần của họ như là chức năng h: X -> Zthỏa mãn h(x) = g(f(x)). Vấn đề với định nghĩa này xảy ra khi kết quả f(x)không được phép là đối số của g.

Trong toán học, những hàm đó không thể được tạo thành nếu không có thêm công việc. Giải pháp toán học nghiêm ngặt cho ví dụ trên của chúng tôi về fglà loại bỏ 0khỏi tập định nghĩa của f. Với tập hợp định nghĩa mới đó (loại mới hạn chế hơn x), ftrở nên có thể kết hợp với g.

Tuy nhiên, nó không thực tế lắm trong lập trình để hạn chế tập hợp các định nghĩa fnhư vậy. Thay vào đó, các ngoại lệ có thể được sử dụng.

Hoặc như cách tiếp cận khác, các giá trị nhân tạo được tạo ra như NaN, undefined, null, Infinityvv Vì vậy, bạn đánh giá 1/0để Infinity1/-0để -Infinity. Và sau đó buộc giá trị mới trở lại biểu thức của bạn thay vì ném ngoại lệ. Dẫn đến các kết quả bạn có thể đoán được hoặc không thể đoán trước được:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

Và chúng tôi đã trở lại với những con số thông thường đã sẵn sàng để tiếp tục;)

JavaScript cho phép chúng ta tiếp tục thực hiện các biểu thức số bằng bất kỳ giá nào mà không gây ra lỗi như trong ví dụ trên. Điều đó có nghĩa là, nó cũng cho phép soạn các hàm. Đó chính xác là những gì đơn nguyên nói về - đó là một quy tắc để tạo các hàm thỏa mãn các tiên đề như được định nghĩa ở đầu câu trả lời này.

Nhưng quy tắc của hàm soạn thảo, phát sinh từ việc triển khai JavaScript để xử lý các lỗi số, có phải là một đơn nguyên không?

Để trả lời câu hỏi này, tất cả những gì bạn cần là kiểm tra các tiên đề (còn lại là bài tập chứ không phải là một phần của câu hỏi ở đây;).

Có thể sử dụng ngoại lệ ném để xây dựng một đơn nguyên không?

Thật vậy, một đơn nguyên hữu ích hơn thay vào đó sẽ là quy tắc quy định rằng nếu fném ngoại lệ cho một số x, thì thành phần của nó với bất kỳ cũng vậy g. Ngoài ra, hãy biến ngoại lệ trở thành Eduy nhất trên toàn cầu với duy nhất một giá trị có thể có ( đối tượng đầu cuối trong lý thuyết danh mục). Bây giờ hai tiên đề có thể được kiểm tra ngay lập tức và chúng tôi nhận được một đơn nguyên rất hữu ích. Và kết quả là những gì được biết đến như là đơn nguyên có thể .

26
Chuck 2010-04-25 09:37.

Đơn nguyên là một kiểu dữ liệu đóng gói một giá trị và về cơ bản, hai phép toán có thể được áp dụng:

  • return x tạo ra một giá trị của kiểu đơn nguyên đóng gói x
  • m >>= f(đọc nó là "toán tử ràng buộc") áp dụng hàm fcho giá trị trong đơn nguyênm

Đó là những gì một đơn nguyên là. Có một số kỹ thuật khác , nhưng về cơ bản hai hoạt động đó xác định một đơn nguyên. Câu hỏi thực sự là, "Đơn nguyên làm gì?", Và điều đó phụ thuộc vào đơn nguyên - danh sách là đơn nguyên, Maybes là đơn nguyên, hoạt động IO là đơn nguyên. Tất cả điều đó có nghĩa là khi chúng tôi nói những thứ đó là đơn nguyên là chúng có giao diện đơn nguyên của return>>=.

16
the_drow 2010-04-25 03:45.

Từ wikipedia :

Trong lập trình hàm, đơn nguyên là một loại kiểu dữ liệu trừu tượng được sử dụng để biểu diễn các phép tính (thay vì dữ liệu trong mô hình miền). Đơn nguyên cho phép người lập trình xâu chuỗi các hành động lại với nhau để xây dựng một đường dẫn, trong đó mỗi hành động được trang trí bằng các quy tắc xử lý bổ sung do đơn nguyên cung cấp. Các chương trình được viết theo kiểu chức năng có thể sử dụng các monads để cấu trúc các thủ tục bao gồm các hoạt động theo trình tự, 1 [2] hoặc để xác định các luồng điều khiển tùy ý (như xử lý đồng thời, liên tục hoặc ngoại lệ).

Về mặt hình thức, một đơn nguyên được xây dựng bằng cách xác định hai hoạt động (liên kết và trả về) và một phương thức khởi tạo kiểu M phải đáp ứng một số thuộc tính để cho phép thành phần chính xác của các hàm đơn nguyên (tức là các hàm sử dụng các giá trị từ đơn nguyên làm đối số của chúng). Hoạt động trả về nhận một giá trị từ một kiểu đơn giản và đặt nó vào một vùng chứa đơn nguyên kiểu M. Hoạt động liên kết thực hiện quy trình ngược lại, trích xuất giá trị ban đầu từ vùng chứa và chuyển nó đến hàm tiếp theo được liên kết trong đường ống.

Một lập trình viên sẽ soạn các hàm đơn nguyên để xác định một đường ống xử lý dữ liệu. Đơn nguyên hoạt động như một khuôn khổ, vì đó là một hành vi có thể tái sử dụng quyết định thứ tự gọi các chức năng đơn nguyên cụ thể trong đường ống và quản lý tất cả các công việc bí mật được tính toán yêu cầu. [3] Các toán tử ràng buộc và trả về xen kẽ trong đường ống sẽ được thực thi sau khi mỗi hàm đơn nguyên trả lại quyền kiểm soát và sẽ quan tâm đến các khía cạnh cụ thể do đơn nguyên xử lý.

Tôi tin rằng nó giải thích nó rất tốt.

13
Gorgi Kosev 2013-12-27 08:35.

Tôi sẽ cố gắng đưa ra định nghĩa ngắn nhất mà tôi có thể quản lý bằng cách sử dụng các thuật ngữ OOP:

Một lớp chung chung CMonadic<T>là một đơn nguyên nếu nó định nghĩa ít nhất các phương thức sau:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

và nếu các luật sau áp dụng cho tất cả các loại T và các giá trị có thể có của chúng t

còn lại danh tính:

CMonadic<T>.create(t).flatMap(f) == f(t)

danh tính đúng đắn

instance.flatMap(CMonadic<T>.create) == instance

sự liên kết:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Ví dụ :

Đơn nguyên Danh sách có thể có:

List<int>.create(1) --> [1]

Và flatMap trong danh sách [1,2,3] có thể hoạt động như vậy:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Các bảng lặp và bảng quan sát cũng có thể được tạo đơn nguyên, cũng như các Lời hứa và Nhiệm vụ.

CHÚ THÍCH :

Đơn nguyên không phức tạp như vậy. Các flatMapchức năng là rất nhiều như thường gặp map. Nó nhận một đối số hàm (còn được gọi là đại biểu), nó có thể gọi (ngay lập tức hoặc sau đó, không hoặc nhiều lần) với một giá trị đến từ lớp chung. Nó mong đợi rằng hàm được truyền cũng sẽ bao bọc giá trị trả về của nó trong cùng một loại lớp chung. Để trợ giúp điều đó, nó cung cấp createmột phương thức khởi tạo có thể tạo một thể hiện của lớp chung đó từ một giá trị. Kết quả trả về của flatMap cũng là một lớp chung cùng loại, thường đóng gói các giá trị giống nhau được chứa trong kết quả trả về của một hoặc nhiều ứng dụng của flatMap với các giá trị đã chứa trước đó. Điều này cho phép bạn chuỗi flatMap bao nhiêu tùy thích:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

Nó chỉ xảy ra rằng loại lớp chung này hữu ích như một mô hình cơ sở cho một số lượng lớn mọi thứ. Điều này (cùng với các biệt ngữ lý thuyết phạm trù) là lý do tại sao các Monads dường như rất khó hiểu hoặc khó giải thích. Chúng là một thứ rất trừu tượng và chỉ trở nên rõ ràng hữu ích khi chúng được chuyên môn hóa.

Ví dụ: bạn có thể lập mô hình ngoại lệ bằng cách sử dụng các vùng chứa đơn nguyên. Mỗi vùng chứa sẽ chứa kết quả của hoạt động hoặc lỗi đã xảy ra. Hàm tiếp theo (đại biểu) trong chuỗi lệnh gọi lại flatMap sẽ chỉ được gọi nếu hàm trước đó đóng gói một giá trị trong vùng chứa. Ngược lại, nếu lỗi được đóng gói, lỗi sẽ tiếp tục lan truyền qua các vùng chứa được xâu chuỗi cho đến khi tìm thấy vùng chứa có chức năng xử lý lỗi được đính kèm thông qua một phương thức được gọi .orElse()(phương thức như vậy sẽ là một phần mở rộng được phép)

Lưu ý : Các ngôn ngữ hàm cho phép bạn viết các hàm có thể hoạt động trên bất kỳ loại nào của một lớp chung đơn nguyên. Để điều này hoạt động, người ta sẽ phải viết một giao diện chung cho các monads. Tôi không biết liệu có thể viết một giao diện như vậy trong C # hay không, nhưng theo tôi biết thì không:

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}
7
nomen 2012-12-01 17:58.

Việc một đơn nguyên có diễn giải "tự nhiên" trong OO hay không phụ thuộc vào đơn nguyên đó. Trong một ngôn ngữ như Java, bạn có thể dịch đơn nguyên có thể sang ngôn ngữ kiểm tra con trỏ null, để các phép tính không thành công (tức là tạo ra Không có gì trong Haskell) sẽ phát ra kết quả là con trỏ null. Bạn có thể dịch đơn nguyên trạng thái sang ngôn ngữ được tạo bằng cách tạo một biến có thể thay đổi và các phương pháp để thay đổi trạng thái của nó.

Một monad là một monoid trong danh mục endofunctors.

Thông tin mà câu đó kết hợp với nhau rất sâu sắc. Và bạn làm việc trong một đơn nguyên với bất kỳ ngôn ngữ mệnh lệnh nào. Đơn nguyên là một ngôn ngữ dành riêng cho miền "được giải trình tự". Nó thỏa mãn một số đặc tính thú vị nhất định, chúng kết hợp với nhau tạo nên một đơn nguyên trở thành một mô hình toán học của "lập trình mệnh lệnh". Haskell giúp bạn dễ dàng xác định các ngôn ngữ mệnh lệnh nhỏ (hoặc lớn), có thể được kết hợp theo nhiều cách khác nhau.

Là một lập trình viên OO, bạn sử dụng hệ thống phân cấp lớp của ngôn ngữ của mình để tổ chức các loại hàm hoặc thủ tục có thể được gọi trong một ngữ cảnh, cái mà bạn gọi là một đối tượng. Một đơn nguyên cũng là một sự trừu tượng trong ý tưởng này, trong chừng mực các đơn nguyên khác nhau có thể được kết hợp theo những cách tùy ý, có hiệu quả "nhập" tất cả các phương thức của đơn nguyên phụ vào phạm vi.

Về mặt kiến ​​trúc, người ta sử dụng chữ ký kiểu để diễn đạt rõ ràng ngữ cảnh nào có thể được sử dụng để tính toán một giá trị.

Người ta có thể sử dụng máy biến áp đơn nguyên cho mục đích này và có một bộ sưu tập chất lượng cao của tất cả các máy biến áp "tiêu chuẩn":

  • Danh sách (tính toán không xác định, bằng cách coi danh sách như một miền)
  • Có thể (tính toán có thể bị lỗi, nhưng báo cáo nào là không quan trọng)
  • Lỗi (tính toán có thể không thành công và yêu cầu xử lý ngoại lệ
  • Trình đọc (tính toán có thể được biểu diễn bằng các thành phần của các hàm Haskell đơn giản)
  • Writer (tính toán với tuần tự "kết xuất" / "ghi nhật ký" (thành chuỗi, html, v.v.)
  • Cont (liên tục)
  • IO (tính toán phụ thuộc vào hệ thống máy tính bên dưới)
  • Trạng thái (các phép tính có ngữ cảnh chứa giá trị có thể sửa đổi)

với các loại máy biến áp đơn nguyên và loại tương ứng. Các lớp kiểu cho phép một cách tiếp cận bổ sung để kết hợp các monad bằng cách thống nhất các giao diện của chúng, để các monad cụ thể có thể triển khai giao diện chuẩn cho "loại" monad. Ví dụ, mô-đun Control.Monad.State chứa một lớp MonadState sm và (State s) là một thể hiện của biểu mẫu

instance MonadState s (State s) where
    put = ...
    get = ...

Câu chuyện dài là đơn nguyên là một bộ hàm gắn "ngữ cảnh" vào một giá trị, có cách đưa giá trị vào đơn nguyên và có cách đánh giá giá trị liên quan đến ngữ cảnh gắn liền với nó, ít nhất là một cách hạn chế.

Vì thế:

return :: a -> m a

là một hàm đưa một giá trị kiểu a vào một "hành động" đơn nguyên của kiểu m a.

(>>=) :: m a -> (a -> m b) -> m b

là một hàm thực hiện một hành động đơn nguyên, đánh giá kết quả của nó và áp dụng một hàm cho kết quả. Điều thú vị về (>> =) là kết quả ở cùng một đơn nguyên. Nói cách khác, trong m >> = f, (>> =) kéo kết quả ra khỏi m và liên kết nó với f, để kết quả là đơn nguyên. (Ngoài ra, chúng ta có thể nói rằng (>> =) kéo f vào m và áp dụng nó cho kết quả.) Kết quả là, nếu chúng ta có f :: a -> mb và g :: b -> mc, chúng ta có thể các hành động "trình tự":

m >>= f >>= g

Hoặc, sử dụng "ký hiệu"

do x <- m
   y <- f x
   g y

Loại cho (>>) có thể đang sáng. Nó là

(>>) :: m a -> m b -> m b

Nó tương ứng với toán tử (;) trong các ngôn ngữ thủ tục như C. Nó cho phép ký hiệu như:

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

Trong logic toán học và triết học, chúng ta có các khung và mô hình, được mô hình hóa một cách "tự nhiên" với thuyết đơn nguyên. Diễn giải là một hàm nhìn vào miền của mô hình và tính toán giá trị chân lý (hoặc tổng quát hóa) của mệnh đề (hoặc công thức, dưới dạng tổng quát hóa). Theo logic phương thức về sự cần thiết, chúng ta có thể nói rằng một mệnh đề là cần thiết nếu nó đúng trong "mọi thế giới có thể xảy ra" - nếu nó đúng đối với mọi miền có thể chấp nhận. Điều này có nghĩa là một mô hình trong ngôn ngữ cho một mệnh đề có thể được sửa đổi thành một mô hình có miền bao gồm tập hợp các mô hình riêng biệt (một mô hình tương ứng với mỗi thế giới có thể có). Mỗi đơn nguyên có một phương thức có tên là "nối" làm phẳng các lớp, điều này ngụ ý rằng mọi hành động đơn nguyên có kết quả là hành động đơn nguyên đều có thể được nhúng vào đơn nguyên.

join :: m (m a) -> m a

Quan trọng hơn, nó có nghĩa là đơn nguyên được đóng theo hoạt động "xếp chồng lớp". Đây là cách hoạt động của máy biến áp đơn nguyên: chúng kết hợp các đơn nguyên bằng cách cung cấp các phương thức "tham gia tương tự" cho các loại như

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

để chúng ta có thể chuyển đổi một hành động trong (Có thể là m) thành một hành động trong m, thu gọn các lớp một cách hiệu quả. Trong trường hợp này, runMaybeT :: Có thểT ma -> m (Có thể a) là phương thức giống như phép nối của chúng ta. (Có thểT m) là một đơn nguyên, và Có thểT :: m (Có thể a) -> Có thểT ma thực sự là một phương thức khởi tạo cho một kiểu hành động đơn nguyên mới trong m.

Đơn nguyên miễn phí cho một đơn nguyên là đơn nguyên được tạo ra bằng cách xếp chồng f, với ngụ ý rằng mọi chuỗi các hàm tạo cho f là một phần tử của đơn nguyên tự do (hoặc chính xác hơn, một cái gì đó có cùng hình dạng với cây các chuỗi các hàm tạo cho f). Các monads tự do là một kỹ thuật hữu ích để xây dựng các monads linh hoạt với số lượng boiler-plate tối thiểu. Trong một chương trình Haskell, tôi có thể sử dụng các monads miễn phí để xác định các monads đơn giản cho "lập trình hệ thống cấp cao" nhằm giúp duy trì sự an toàn của kiểu (Tôi chỉ đang sử dụng các kiểu và khai báo của chúng. Việc triển khai được thực hiện ngay với việc sử dụng tổ hợp):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Chủ nghĩa đơn nguyên là kiến ​​trúc cơ bản cho cái mà bạn có thể gọi là mẫu "trình thông dịch" hoặc "lệnh", được trừu tượng hóa ở dạng rõ ràng nhất, vì mọi tính toán đơn nguyên phải được "chạy", ít nhất là tầm thường. (Hệ thống thời gian chạy chạy đơn nguyên IO cho chúng tôi và là điểm vào của bất kỳ chương trình Haskell nào. IO "điều khiển" phần còn lại của các tính toán, bằng cách chạy các hành động IO theo thứ tự).

Kiểu cho phép nối cũng là nơi chúng ta nhận được tuyên bố rằng một đơn nguyên là một đơn nguyên trong danh mục các trình kết thúc. Tham gia thường quan trọng hơn cho các mục đích lý thuyết, dựa trên loại của nó. Nhưng hiểu loại có nghĩa là hiểu đơn nguyên. Các kiểu nối giống như phép nối của máy biến áp đơn nguyên là các cấu tạo hiệu quả của các máy biến áp cuối, theo nghĩa cấu tạo chức năng. Để đặt nó bằng một ngôn ngữ giả giống Haskell,

Foo :: m (ma) <-> (m. M) a

3
David K. Hess 2012-12-24 07:53.

Các đơn nguyên trong cách sử dụng điển hình là chức năng tương đương với các cơ chế xử lý ngoại lệ của lập trình thủ tục.

Trong các ngôn ngữ thủ tục hiện đại, bạn đặt một trình xử lý ngoại lệ xung quanh một chuỗi các câu lệnh, bất kỳ câu lệnh nào trong số đó có thể tạo ra một ngoại lệ. Nếu bất kỳ câu lệnh nào đưa ra một ngoại lệ, thì việc thực hiện bình thường của chuỗi các câu lệnh sẽ tạm dừng và chuyển đến một trình xử lý ngoại lệ.

Tuy nhiên, về mặt triết học, các ngôn ngữ lập trình hàm tránh các tính năng xử lý ngoại lệ do bản chất giống như "goto" của chúng. Quan điểm lập trình chức năng là các chức năng không được có "tác dụng phụ" như các trường hợp ngoại lệ làm gián đoạn dòng chương trình.

Trên thực tế, không thể loại trừ tác dụng phụ trong thế giới thực do chủ yếu do I / O. Các đơn nguyên trong lập trình hàm được sử dụng để xử lý điều này bằng cách lấy một tập hợp các lệnh gọi hàm được xâu chuỗi (bất kỳ lệnh nào trong số đó có thể tạo ra kết quả không mong muốn) và biến bất kỳ kết quả không mong muốn nào thành dữ liệu được đóng gói mà vẫn có thể lưu chuyển an toàn qua các lệnh gọi hàm còn lại.

Luồng kiểm soát được giữ nguyên nhưng sự kiện bất ngờ được gói gọn và xử lý một cách an toàn.

3
cibercitizen1 2014-02-17 06:11.

Đơn nguyên là một mảng các hàm

(Pst: một mảng các hàm chỉ là một phép tính).

Trên thực tế, thay vì một mảng true (một hàm trong một mảng ô), bạn có các hàm đó được xâu chuỗi bởi một hàm khác >> =. Dấu >> = cho phép điều chỉnh các kết quả từ hàm i sang hàm i + 1, thực hiện các phép tính giữa chúng hoặc thậm chí, không gọi hàm i + 1.

Các loại được sử dụng ở đây là "loại có ngữ cảnh". Đây là, một giá trị có "thẻ". Các hàm đang được xâu chuỗi phải nhận "giá trị rỗng" và trả về kết quả được gắn thẻ. Một trong những nhiệm vụ của >> = là trích xuất một giá trị trần ra khỏi ngữ cảnh của nó. Ngoài ra còn có chức năng "return", nhận một giá trị không và đặt nó với một thẻ.

Một ví dụ với Có thể . Hãy sử dụng nó để lưu trữ một số nguyên đơn giản để thực hiện các phép tính.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

Để chỉ ra rằng monads là một mảng các hàm với các hoạt động trợ giúp, hãy xem xét tương đương với ví dụ trên, chỉ sử dụng một mảng thực sự của các hàm

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

Và nó sẽ được sử dụng như thế này:

print (runMyMonad (Just 160) myArray1)
3
sea-rob 2016-11-30 11:33.

Theo thuật ngữ OO, đơn nguyên là một vật chứa thông thạo.

Yêu cầu tối thiểu là một định nghĩa class <A> Somethinghỗ trợ một hàm tạo Something(A a)và ít nhất một phương thứcSomething<B> flatMap(Function<A, Something<B>>)

Có thể cho rằng, nó cũng tính nếu lớp đơn nguyên của bạn có bất kỳ phương thức nào có chữ ký Something<B> work()bảo lưu các quy tắc của lớp - trình biên dịch đặt trong flatMap tại thời điểm biên dịch.

Tại sao đơn nguyên hữu ích? Bởi vì nó là một thùng chứa cho phép các hoạt động có khả năng chuỗi mà vẫn bảo toàn ngữ nghĩa. Ví dụ, Optional<?>bảo ngữ nghĩa của isPresent cho Optional<String>, Optional<Integer>, Optional<MyClass>vv

Như một ví dụ sơ bộ,

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

Lưu ý rằng chúng ta bắt đầu bằng một chuỗi và kết thúc bằng một số nguyên. Tuyệt đấy.

Trong OO, có thể hơi vẫy tay, nhưng bất kỳ phương thức nào trên Something trả về một lớp con khác của Something đều đáp ứng tiêu chí của một hàm container trả về một vùng chứa kiểu ban đầu.

Đó là cách bạn bảo toàn ngữ nghĩa - tức là ý nghĩa và hoạt động của vùng chứa không thay đổi, chúng chỉ bọc và nâng cao đối tượng bên trong vùng chứa.

2
Richard Berg 2010-04-26 05:18.

Nếu bạn đã từng sử dụng Powershell, thì các mẫu mà Eric mô tả sẽ có vẻ quen thuộc. Powershell cmdlet là đơn nguyên; thành phần chức năng được đại diện bởi một đường ống .

Cuộc phỏng vấn của Jeffrey Snover với Erik Meijer đi vào chi tiết hơn.

2
Ira 2019-04-22 04:11.

Một lời giải thích với nghiên cứu trường hợp của Marvel đơn giản Monads là ở đây .

Đơn nguyên là những trừu tượng được sử dụng để sắp xếp các hàm phụ thuộc có hiệu lực. Có hiệu lực ở đây có nghĩa là chúng trả về một kiểu ở dạng F [A], ví dụ Option [A] trong đó Option là F, được gọi là hàm tạo kiểu. Hãy xem điều này trong 2 bước đơn giản

  1. Bên dưới Thành phần chức năng là bắc cầu. Vì vậy để đi từ A đến CI có thể soạn A => B và B => C.
 A => C   =   A => B  andThen  B => C

  1. Tuy nhiên, nếu hàm trả về kiểu hiệu ứng như Option [A] tức là A => F [B] thì thành phần không hoạt động khi đến B, chúng ta cần A => B nhưng chúng ta có A => F [B].

    Chúng ta cần một toán tử đặc biệt, "bind" biết cách kết hợp các hàm này để trả về F [A].

 A => F[C]   =   A => F[B]  bind  B => F[C]

Hàm "bind" được định nghĩa cho F cụ thể .

Ngoài ra còn có "return" , kiểu A => F [A] cho bất kỳ A nào , cũng được xác định cho F cụ thể đó . Để trở thành Đơn nguyên, F phải có hai hàm này được định nghĩa cho nó.

Do đó, chúng ta có thể xây dựng một hàm hiệu dụng A => F [B] từ bất kỳ hàm thuần túy nào A => B ,

 A => F[B]   =   A => B  andThen  return

nhưng một F nhất định cũng có thể xác định các chức năng đặc biệt "tích hợp sẵn" không rõ ràng của riêng nó thuộc các loại mà người dùng không thể tự định nghĩa chúng (bằng một ngôn ngữ thuần túy ), như

  • "ngẫu nhiên" ( Phạm vi => Ngẫu nhiên [Int] )
  • "in" ( Chuỗi => IO [()] )
  • "thử ... bắt", v.v.
2
Gulshan 2019-07-22 00:34.

Tôi đang chia sẻ hiểu biết của mình về Monads, có thể không hoàn hảo về mặt lý thuyết. Đơn nguyên là về sự lan truyền theo ngữ cảnh . Đơn giản là, bạn xác định một số ngữ cảnh cho một số dữ liệu (hoặc (các) kiểu dữ liệu), và sau đó xác định cách ngữ cảnh đó sẽ được mang theo dữ liệu trong suốt quá trình xử lý của nó. Và việc xác định lan truyền ngữ cảnh chủ yếu là xác định cách hợp nhất nhiều ngữ cảnh (cùng loại). Sử dụng Monads cũng có nghĩa là đảm bảo những ngữ cảnh này không vô tình bị loại bỏ khỏi dữ liệu. Mặt khác, dữ liệu không có ngữ cảnh khác có thể được đưa vào bối cảnh mới hoặc hiện tại. Sau đó, khái niệm đơn giản này có thể được sử dụng để đảm bảo tính đúng đắn về thời gian biên dịch của một chương trình.

1
Jordan 2015-03-08 23:43.

Xem câu trả lời của tôi cho "Đơn nguyên là gì?"

Nó bắt đầu với một ví dụ thúc đẩy, hoạt động thông qua ví dụ, lấy một ví dụ về đơn nguyên và chính thức định nghĩa "đơn nguyên".

Nó giả định không có kiến ​​thức về lập trình hàm và nó sử dụng mã giả với function(argument) := expressioncú pháp với các biểu thức đơn giản nhất có thể.

Chương trình C ++ này là một triển khai của đơn nguyên mã giả. (Đối với tham chiếu: Mlà phương thức khởi tạo kiểu, feedlà hoạt động "ràng buộc" và wraplà hoạt động "trả về".)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}
0
novis 2013-04-06 05:17.

Từ quan điểm thực tế (tóm tắt những gì đã được nói trong nhiều câu trả lời trước đây và các bài viết liên quan), đối với tôi, dường như một trong những "mục đích" cơ bản (hoặc tính hữu ích) của monad là tận dụng các phụ thuộc tiềm ẩn trong các lệnh gọi phương thức đệ quy. hay còn gọi là thành phần hàm (tức là khi f1 gọi f2 gọi f3, f3 cần được đánh giá trước f2 trước f1) để biểu diễn thành phần tuần tự theo cách tự nhiên, đặc biệt trong bối cảnh của mô hình đánh giá lười biếng (nghĩa là, thành phần tuần tự như một chuỗi thuần , ví dụ: "f3 (); f2 (); f1 ();" trong C - mẹo đặc biệt rõ ràng nếu bạn nghĩ đến trường hợp f3, f2 và f1 thực sự không trả về gì [chuỗi của chúng là f1 (f2 (f3)) là nhân tạo, hoàn toàn nhằm mục đích tạo ra trình tự]).

Điều này đặc biệt có liên quan khi có liên quan đến các tác dụng phụ, tức là khi một số trạng thái bị thay đổi (nếu f1, f2, f3 không có tác dụng phụ, thì chúng sẽ được đánh giá theo thứ tự nào; đó là một tính chất tuyệt vời của nguyên chất ngôn ngữ chức năng, để có thể song song hóa các tính toán đó chẳng hạn). Các chức năng thuần túy càng tốt.

Tôi nghĩ rằng từ quan điểm hạn hẹp đó, monads có thể được coi là đường cú pháp cho các ngôn ngữ thiên về đánh giá lười biếng (chỉ đánh giá mọi thứ khi thực sự cần thiết, theo một thứ tự không dựa trên trình bày của mã), và điều đó không có các phương tiện khác để biểu diễn thành phần tuần tự. Kết quả thực là các phần mã "không tinh khiết" (tức là có tác dụng phụ) có thể được trình bày một cách tự nhiên, theo cách bắt buộc, nhưng được tách biệt rõ ràng khỏi các chức năng thuần túy (không có tác dụng phụ), có thể đánh giá một cách lười biếng.

Đây chỉ là một khía cạnh, như đã cảnh báo ở đây .

0
RedPoppy 2019-11-10 23:24.

Lời giải thích đơn giản nhất mà tôi có thể nghĩ đến là monads là một cách tạo các hàm với các kết quả phù hợp (hay còn gọi là thành phần Kleisli). Một hàm "embelished" có chữ ký a -> (b, smth)trong đó abcác loại (nghĩ Int, Bool) có thể khác nhau, nhưng không nhất thiết - và smthlà "ngữ cảnh" hoặc "embelishment".

Đây là loại chức năng cũng có thể được viết a -> m bở đâu mlà tương đương với "embelishment" smth. Vì vậy, đây là các hàm trả về giá trị trong ngữ cảnh (hãy nghĩ các hàm ghi nhật ký hành động của chúng, smththông báo ghi nhật ký ở đâu; hoặc các hàm thực hiện đầu vào \ đầu ra và kết quả của chúng phụ thuộc vào kết quả của hành động IO).

Đơn nguyên là một giao diện ("typeclass") làm cho người triển khai chỉ dẫn cách soạn các hàm như vậy. Người triển khai cần xác định một hàm tổng hợp (a -> m b) -> (b -> m c) -> (a -> m c)cho bất kỳ kiểu mnào muốn triển khai giao diện (đây là thành phần Kleisli).

Vì vậy, nếu chúng ta nói rằng chúng ta có một loại tuple (Int, String)đại diện cho các kết quả tính toán trên Ints cũng ghi lại các hành động của chúng, với (_, String)là "embelishment" - nhật ký của hành động - và hai hàm increment :: Int -> (Int, String)twoTimes :: Int -> (Int, String)chúng ta muốn có được một hàm incrementThenDouble :: Int -> (Int, String)là thành phần của hai chức năng cũng tính đến các bản ghi.

Trong ví dụ đã cho, một triển khai đơn nguyên của hai hàm áp dụng cho giá trị số nguyên 2 incrementThenDouble 2(bằng twoTimes (increment 2)) sẽ trả về (6, " Adding 1. Doubling 3.")kết quả trung gian increment 2bằng (3, " Adding 1.")twoTimes 3bằng(6, " Doubling 3.")

Từ hàm thành phần Kleisli này, người ta có thể suy ra các hàm đơn nguyên thông thường.

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Một thương hiệu kế thừa khác có được cuộc sống mới khi những người chủ mới của Black Opal có được Hội chợ thời trang

Một thương hiệu kế thừa khác có được cuộc sống mới khi những người chủ mới của Black Opal có được Hội chợ thời trang

Desiree Rogers, trái, và Cheryl Mayberry McKissack Chuyển sang, Fenty Beauty và Pat McGrath, có một đế chế làm đẹp mới do phụ nữ làm chủ đang trỗi dậy. Sau thương vụ mua lại mỹ phẩm Black Opal vào tháng 9, đội ngũ quyền lực của Giám đốc điều hành công ty Desiree Rogers và Chủ tịch Cheryl Mayberry McKissack đã thông báo mua lại thương hiệu làm đẹp tiên phong Fashion Fair từ người sáng lập Johnson Publishing Company (JPC).

Ngoài việc trở nên vô nghĩa, các bên bộc lộ giới tính giờ đây đã trở nên chết người

Ngoài việc trở nên vô nghĩa, các bên bộc lộ giới tính giờ đây đã trở nên chết người

Đối với những người có quá nhiều thời gian, tiệc tiết lộ giới tính là một cách thú vị để không cần áp đặt những định kiến ​​hạn chế lên thai nhi trước một đối tượng bạn bè giả vờ quan tâm một cách lịch sự. Tuy nhiên, việc chỉ dựa vào những chiếc bánh nướng có màu nhân tạo để biểu thị một tiếng hoo-ha hoặc một đứa trẻ đi tè đã trở nên xa xỉ trong các bậc cha mẹ trên Instagram.

Kirstjen Nielsen đang cố gắng đổi mới bản thân với tư cách là một người phụ nữ 'nói ra sự thật cho sức mạnh'

Kirstjen Nielsen đang cố gắng đổi mới bản thân với tư cách là một người phụ nữ 'nói ra sự thật cho sức mạnh'

Hôm thứ Tư, cựu Bộ trưởng An ninh Nội địa Kirstjen Nielsen vì một lý do nào đó đã được đưa ra một diễn đàn để phát biểu tại Hội nghị thượng đỉnh Những người phụ nữ quyền lực nhất của Fortune. Và có thể đoán trước được, người phụ nữ giám sát các nỗ lực (đang diễn ra) của chính quyền Trump nhằm chia cắt các gia đình ở biên giới đã sử dụng thời gian của mình như một cơ hội để tự bảo vệ mình và tự nhận mình là người — và hãy hít thở sâu ở đây — “đã nói sự thật với quyền lực.

Toyota Racing Development sẽ cho ra mắt Supra 3000GT Concept, Massive Wing vào tháng tới

Toyota Racing Development sẽ cho ra mắt Supra 3000GT Concept, Massive Wing vào tháng tới

Vì thế hệ mới nhất của chiếc xe điều chỉnh được yêu thích trên thế giới hiện đã có mặt tại đây, nên chỉ có điều kiện là Toyota Racing Development sẽ đưa phiên bản Supra thế hệ thứ năm sửa đổi của riêng mình đến SEMA vào tháng tới. Toyota đã cho chúng tôi một gợi ý về những gì sẽ xảy ra trong tuần này: một chiếc Concept GR Supra 3000GT hiện đại.

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.

Language