Đã một năm kể từ khi tôi đăng câu hỏi này. Sau khi đăng nó, tôi đã đi sâu vào Haskell trong vài tháng. Tôi rất thích nó, nhưng tôi đặt nó sang một bên khi tôi đã sẵn sàng đi sâu vào Monads.Tôi trở lại làm việc và tập trung vào các công nghệ mà dự án của tôi yêu cầu.
Và đêm qua, tôi đã đến và đọc lại các câu trả lời này. Quan trọng nhất là, tôi đã đọc lại the specific C# example trong các nhận xét văn bản của the Brian Beckman video ai đó mentions above. Nó hoàn toàn rõ ràng và chiếu sáng mà tôi đã quyết định đăng trực tiếp tại đây.
Do nhận xét này, không chỉ làm tôi cảm thấy như tôi hiểu chính xác gì Monads là ... Tôi nhận ra tôi đã thực sự viết một số thứ trong C# mà là Monads ... hoặc ít nhất là rất gần, và phấn đấu để giải quyết các vấn đề tương tự.
Vì vậy, đây là những nhận xét - đây là tất cả các trích dẫn trực tiếp từ the comment here bởi sylvan:
này là khá mát mẻ. Đó là một chút trừu tượng mặc dù. Tôi có thể tưởng tượng những người không biết những gì monads đã bị nhầm lẫn do thiếu các ví dụ thực tế.
Vì vậy, hãy để tôi cố gắng tuân thủ, và chỉ để thực sự rõ ràng tôi sẽ làm một ví dụ trong C#, mặc dù nó sẽ trông xấu xí. Tôi sẽ thêm Haskell tương đương vào cuối và cho bạn thấy đường cú pháp Haskell tuyệt vời, ở đâu, IMO, monads thực sự bắt đầu có ích.
Được rồi, vì vậy một trong những Monads đơn giản nhất được gọi là "Có thể đơn nguyên" trong Haskell. Trong C#, loại Có thể được gọi là Nullable<T>
. Về cơ bản, nó là một lớp nhỏ chỉ gói gọn khái niệm về một giá trị hợp lệ và có giá trị, hoặc là "null" và không có giá trị.
Một điều hữu ích để gắn bên trong một đơn nguyên để kết hợp các giá trị thuộc loại này là khái niệm thất bại. I E. chúng tôi muốn có thể xem xét nhiều giá trị nullable và trả lại null
ngay sau khi bất kỳ giá trị nào trong số đó là null. Điều này có thể hữu ích nếu bạn, ví dụ, tra nhiều khóa trong từ điển hay gì đó, và cuối cùng bạn muốn xử lý tất cả các kết quả và kết hợp chúng bằng cách nào đó, nhưng nếu bất kỳ khóa nào không có trong từ điển, bạn muốn trả lại null
cho toàn bộ điều. Nó sẽ là tẻ nhạt để tự kiểm tra từng tra cứu cho null
và trả về, vì vậy chúng ta có thể ẩn kiểm tra này bên trong toán tử liên kết (đó là loại điểm của monads, chúng ta che giấu sổ sách trong toán tử bind. để sử dụng vì chúng ta có thể quên chi tiết).
Đây là chương trình khuyến khích toàn bộ sự việc (tôi sẽ xác định Bind
sau này, đây chỉ là để cho bạn thấy lý do tại sao nó đẹp).
class Program
{
static Nullable<int> f(){ return 4; }
static Nullable<int> g(){ return 7; }
static Nullable<int> h(){ return 9; }
static void Main(string[] args)
{
Nullable<int> z =
f().Bind(fval =>
g().Bind(gval =>
h().Bind(hval =>
new Nullable<int>(fval + gval + hval))));
Console.WriteLine(
"z = {0}", z.HasValue ? z.Value.ToString() : "null");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
Bây giờ, bỏ qua trong chốc lát rằng đã có sự ủng hộ để làm điều này cho Nullable
trong C# (bạn có thể thêm ints nullable với nhau và bạn nhận được null nếu một trong hai là null). Hãy giả vờ rằng không có tính năng như vậy, và nó chỉ là một lớp người dùng định nghĩa không có phép thuật đặc biệt. Vấn đề là chúng ta có thể sử dụng hàm Bind
để liên kết một biến với nội dung của giá trị Nullable
và giả sử rằng không có gì lạ xảy ra và sử dụng chúng như ints bình thường và chỉ cần thêm chúng lại với nhau. Chúng tôi bọc kết quả trong một nullable ở cuối, và nullable sẽ là null (nếu bất kỳ f
, g
hoặc h
trả về null) hoặc nó sẽ là kết quả của việc tổng hợp f
, g
và h
cùng nhau. (điều này tương tự như cách chúng ta có thể ràng buộc một hàng trong một cơ sở dữ liệu với một biến trong LINQ, và làm việc với nó, an toàn khi biết rằng toán tử Bind
sẽ đảm bảo rằng biến sẽ chỉ được thông qua giá trị hàng hợp lệ).
Bạn có thể chơi với điều này và thay đổi bất kỳ số f
, g
và h
để trả về null và bạn sẽ thấy toàn bộ điều sẽ trả về giá trị rỗng. Vì vậy, rõ ràng các nhà điều hành liên kết đã làm điều này kiểm tra cho chúng tôi, và bảo lãnh trả lại null nếu nó gặp một giá trị null, và nếu không vượt qua cùng giá trị bên trong cấu trúc Nullable
vào lambda.
Dưới đây là các nhà điều hành Bind
:
public static Nullable<B> Bind<A,B>(this Nullable<A> a, Func<A,Nullable<B>> f)
where B : struct
where A : struct
{
return a.HasValue ? f(a.Value) : null;
}
Các loại ở đây cũng giống như trong đoạn video. Phải mất một cú pháp M a
(Nullable<A>
trong C# cho trường hợp này) và hàm từ a
đến M b
(Func<A, Nullable<B>>
theo cú pháp C#) và trả về một M b
(Nullable<B>
).
Mã này chỉ kiểm tra nếu giá trị rỗng có chứa một giá trị và nếu trích xuất nó và chuyển nó vào hàm, nếu không nó sẽ trả về giá trị rỗng. Điều này có nghĩa là toán tử Bind
sẽ xử lý tất cả logic kiểm tra null cho chúng tôi. Nếu và chỉ khi giá trị mà chúng tôi gọi là Bind
trên là không null thì giá trị đó sẽ được "chuyển cùng" sang hàm lambda, nếu không chúng tôi sẽ gửi tiền sớm và toàn bộ biểu thức là không. Điều này cho phép mã mà chúng tôi viết bằng cách sử dụng đơn nguyên hoàn toàn không có hành vi kiểm tra không có giá trị này, chúng tôi chỉ sử dụng Bind
và nhận một biến ràng buộc với giá trị bên trong giá trị đơn nguyên (fval
, gval
và hval
trong mã ví dụ) và chúng tôi có thể sử dụng chúng an toàn khi biết rằng Bind
sẽ xử lý việc kiểm tra chúng trước khi chuyển chúng.
Có các ví dụ khác về những điều bạn có thể làm với một đơn nguyên. Ví dụ: bạn có thể làm cho toán tử Bind
xử lý luồng đầu vào của các ký tự và sử dụng nó để viết trình kết hợp phân tích cú pháp. Mỗi trình kết hợp phân tích cú pháp sau đó có thể hoàn toàn không biết gì về những thứ như theo dõi lại, lỗi phân tích cú pháp, và chỉ kết hợp các trình phân tích cú pháp nhỏ hơn với nhau như thể mọi thứ sẽ không bao giờ sai, an toàn với kiến thức rằng việc thực hiện một cách thông minh của Bind
các bit khó. Sau đó, sau đó có thể ai đó thêm đăng nhập vào đơn nguyên, nhưng mã sử dụng đơn nguyên không thay đổi, bởi vì tất cả phép thuật xảy ra trong định nghĩa của toán tử Bind
, phần còn lại của mã không thay đổi.
Cuối cùng, đây là việc triển khai cùng một mã trong Haskell (--
bắt đầu dòng nhận xét).
-- Here's the data type, it's either nothing, or "Just" a value
-- this is in the standard library
data Maybe a = Nothing | Just a
-- The bind operator for Nothing
Nothing >>= f = Nothing
-- The bind operator for Just x
Just x >>= f = f x
-- the "unit", called "return"
return = Just
-- The sample code using the lambda syntax
-- that Brian showed
z = f >>= (\fval ->
g >>= (\gval ->
h >>= (\hval -> return (fval+gval+hval))))
-- The following is exactly the same as the three lines above
z2 = do
fval <- f
gval <- g
hval <- h
return (fval+gval+hval)
Như bạn có thể thấy ký hiệu đẹp ở cuối làm cho nó trông giống như mã mệnh lệnh thẳng. Và thực sự điều này là do thiết kế. Monads có thể được sử dụng để đóng gói tất cả các công cụ hữu ích trong lập trình bắt buộc (trạng thái có thể thay đổi, IO, v.v.) và sử dụng cú pháp giống như mệnh lệnh này, nhưng đằng sau rèm cửa, tất cả chỉ là các monads và thực hiện thông minh toán tử bind! Điều thú vị là bạn có thể thực hiện các monads của riêng mình bằng cách triển khai >>=
và return
. Và nếu bạn làm như vậy những monads cũng sẽ có thể sử dụng ký hiệu do
, có nghĩa là bạn về cơ bản có thể viết các ngôn ngữ nhỏ của riêng bạn bằng cách chỉ định nghĩa hai hàm!
Xin lưu ý rằng nó thực sự là một nhà phát triển C# 3.0. Đừng nhầm lẫn với .NET 3.5. Ngoài ra, câu hỏi hay. – Razzie
Đó là giá trị chỉ ra rằng biểu thức truy vấn LINQ là một ví dụ về hành vi monadic trong C# 3. –
Tôi vẫn nghĩ rằng đó là một câu hỏi trùng lặp. Một trong những câu trả lời trong http://stackoverflow.com/questions/2366/can-anyone-explain-monads liên kết tới http://channel9vip.orcsweb.com/shows/Going+Deep/Brian-Beckman-Dont-fear- the Monads /, trong đó một trong các bình luận có một ví dụ C# rất đẹp. :) – jalf