Trong C #, một đơn nguyên là gì?


189

Có rất nhiều cuộc nói chuyện về các đơn nguyên ngày nay. Tôi đã đọc một vài bài viết / bài đăng trên blog, nhưng tôi không thể đi đủ xa với các ví dụ của họ để nắm bắt hoàn toàn khái niệm này. Lý do là các đơn nguyên là một khái niệm ngôn ngữ chức năng, và do đó các ví dụ là trong các ngôn ngữ tôi chưa từng làm việc (vì tôi chưa sử dụng ngôn ngữ chức năng chuyên sâu). Tôi không thể nắm bắt cú pháp đủ sâu để theo dõi các bài viết đầy đủ ... nhưng tôi có thể nói có điều gì đó đáng để hiểu ở đó.

Tuy nhiên, tôi biết C # khá tốt, bao gồm các biểu thức lambda và các tính năng chức năng khác. Tôi biết C # chỉ có một tập hợp các tính năng chức năng và vì vậy có lẽ các đơn nguyên không thể được thể hiện trong C #.

Tuy nhiên, chắc chắn là có thể truyền đạt khái niệm? Ít nhất tôi hy vọng như vậy. Có lẽ bạn có thể trình bày một ví dụ C # làm nền tảng và sau đó mô tả những gì nhà phát triển C # muốn anh ta có thể làm từ đó nhưng không thể vì ngôn ngữ thiếu các tính năng lập trình chức năng. Điều này sẽ là tuyệt vời, bởi vì nó sẽ truyền đạt ý định và lợi ích của các đơn nguyên. Vì vậy, đây là câu hỏi của tôi: lời giải thích tốt nhất bạn có thể đưa ra về các đơn vị cho nhà phát triển C # 3 là gì?

Cảm ơn!

(EDIT: Nhân tiện, tôi biết có ít nhất 3 câu hỏi "đơn vị là gì" đã có trên SO. Tuy nhiên, tôi phải đối mặt với cùng một vấn đề với họ ... vì vậy câu hỏi này là cần thiết, vì nhà phát triển C # tập trung. Cảm ơn.)


Xin lưu ý rằng nó thực sự là một nhà phát triển C # 3.0. Đừng nhầm với .NET 3.5. Ngoài ra, câu hỏi hay.
Razzie

4
Thật đáng để chỉ ra rằng các biểu thức truy vấn LINQ là một ví dụ về hành vi đơn điệu trong C # 3.
Erik Forbes

1
Tôi vẫn nghĩ đó là một câu hỏi trùng lặp. Một trong những câu trả lời trong stackoverflow.com/questions/2366/can-anyone-explain-monads liên kết đến channel9vip.orcsweb.com/shows/Going+Deep/iêu , trong đó một trong những bình luận có ví dụ C # rất hay. :)
jalf

4
Tuy nhiên, đó chỉ là một liên kết từ một câu trả lời cho một trong những câu hỏi SO. Tôi thấy giá trị trong một câu hỏi tập trung vào các nhà phát triển C #. Đó là điều tôi sẽ hỏi một lập trình viên chức năng đã từng làm C # nếu tôi biết, vì vậy có vẻ hợp lý khi hỏi nó trên SO. Nhưng tôi tôn trọng quyền của bạn đối với ý kiến ​​của bạn là tốt.
Charlie Hoa

1
Không phải là một câu trả lời tất cả những gì bạn cần? ;) Quan điểm của tôi chỉ là một trong những câu hỏi khác (và bây giờ là câu hỏi này, vì vậy yay) đã có câu trả lời cụ thể C # (có vẻ như thực sự được viết rất tốt, thực sự. Có lẽ là lời giải thích tốt nhất tôi từng thấy)
jalf

Câu trả lời:


147

Hầu hết những gì bạn làm trong lập trình cả ngày là kết hợp một số chức năng lại với nhau để xây dựng các chức năng lớn hơn từ chúng. Thông thường, bạn không chỉ có các chức năng trong hộp công cụ của mình mà còn các thứ khác như toán tử, phép gán biến và tương tự, nhưng nhìn chung chương trình của bạn kết hợp nhiều "tính toán" với các tính toán lớn hơn sẽ được kết hợp với nhau hơn nữa.

Một đơn nguyên là một số cách để thực hiện "kết hợp tính toán" này.

Thông thường "toán tử" cơ bản nhất của bạn để kết hợp hai tính toán với nhau là ;:

a; b

Khi bạn nói điều này bạn có nghĩa là "đầu tiên làm a, sau đó làm b". Kết quả a; bvề cơ bản lại là một tính toán có thể được kết hợp cùng với nhiều thứ hơn. Đây là một đơn nguyên đơn giản, nó là một cách kết hợp các tính toán nhỏ cho những cái lớn hơn. Câu ;nói "làm điều bên trái, sau đó làm điều bên phải".

Một điều khác có thể được xem như là một đơn nguyên trong các ngôn ngữ hướng đối tượng là .. Thường thì bạn tìm thấy những thứ như thế này:

a.b().c().d()

Các .cơ bản có nghĩa là "đánh giá tính toán ở bên trái, và sau đó gọi phương thức trên phải vào kết quả của điều đó". Đó là một cách khác để kết hợp các chức năng / tính toán với nhau, phức tạp hơn một chút so với ;. Và khái niệm xâu chuỗi mọi thứ cùng với nhau .là một đơn nguyên, vì đó là cách kết hợp hai tính toán với nhau để tính toán mới.

Một đơn nguyên khá phổ biến khác, không có cú pháp đặc biệt, là mẫu này:

rv = socket.bind(address, port);
if (rv == -1)
  return -1;

rv = socket.connect(...);
if (rv == -1)
  return -1;

rv = socket.send(...);
if (rv == -1)
  return -1;

Giá trị trả về -1 biểu thị thất bại, nhưng không có cách nào thực sự để trừu tượng hóa việc kiểm tra lỗi này, ngay cả khi bạn có nhiều lệnh gọi API mà bạn cần kết hợp theo cách này. Về cơ bản, đây chỉ là một đơn nguyên khác kết hợp các hàm gọi theo quy tắc "nếu hàm bên trái trả về -1, hãy tự trả về -1, nếu không thì gọi hàm bên phải". Nếu chúng ta có một toán tử >>=đã làm điều này, chúng ta có thể viết:

socket.bind(...) >>= socket.connect(...) >>= socket.send(...)

Nó sẽ làm cho mọi thứ dễ đọc hơn và giúp trừu tượng hóa cách kết hợp các chức năng đặc biệt của chúng tôi, do đó chúng tôi không cần phải lặp đi lặp lại nhiều lần.

Và còn nhiều cách khác để kết hợp các hàm / tính toán hữu ích như một mô hình chung và có thể được trừu tượng hóa trong một đơn nguyên, cho phép người dùng của đơn vị viết mã ngắn gọn và rõ ràng hơn, vì tất cả việc giữ và quản lý sách các chức năng được sử dụng được thực hiện trong đơn nguyên.

Ví dụ, phần trên >>=có thể được mở rộng để "thực hiện kiểm tra lỗi và sau đó gọi phía bên phải trên ổ cắm mà chúng tôi nhận làm đầu vào", do đó chúng tôi không cần chỉ định rõ ràng socketnhiều lần:

new socket() >>= bind(...) >>= connect(...) >>= send(...);

Định nghĩa chính thức phức tạp hơn một chút vì bạn phải lo lắng về việc làm thế nào để lấy kết quả của một chức năng làm đầu vào cho chức năng tiếp theo, nếu chức năng đó cần đầu vào đó và vì bạn muốn đảm bảo rằng các chức năng bạn kết hợp phù hợp với cách bạn cố gắng kết hợp chúng trong đơn nguyên của bạn. Nhưng khái niệm cơ bản chỉ là bạn chính thức hóa các cách khác nhau để kết hợp các chức năng lại với nhau.


28
Câu trả lời chính xác! Tôi sẽ đưa ra một trích dẫn của Oliver Steele, cố gắng liên kết Monads với toán tử quá tải à la C ++ hoặc C #: Monads cho phép bạn quá tải ';' nhà điều hành.
Jörg W Mittag

6
@ JörgWMittag Tôi đã đọc câu nói đó trước đây, nhưng nghe có vẻ vô nghĩa quá mức. Bây giờ tôi đã hiểu các đơn nguyên và đọc lời giải thích này về cách ';' là một, tôi hiểu rồi Nhưng tôi nghĩ rằng đó thực sự là một tuyên bố phi lý cho hầu hết các nhà phát triển cấp bách. ';' không được xem là một toán tử nữa so với // là hầu hết.
Jimmy Hoffa

2
Bạn có chắc chắn rằng bạn biết một đơn nguyên là gì? Monads không phải là "hàm" hay tính toán, có các quy tắc cho monads.
Luis

Trong ;ví dụ của bạn : Những đối tượng / loại dữ liệu nào ;ánh xạ? (Nghĩ Listbản đồ Tđến List<T>) Làm thế nào để ;ánh xạ hình thái / chức năng giữa các đối tượng / kiểu dữ liệu? Thế nào là pure, join, bindcho ;?
Micha Wiedenmann

44

Đã một năm kể từ khi tôi đăng câu hỏi này. Sau khi đăng nó, tôi đào sâu vào Haskell trong một vài tháng. Tôi rất thích nó, nhưng tôi đã đặt nó sang một bên ngay 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 những phản hồi này. Quan trọng nhất , tôi đọc lại ví dụ C # cụ thể trong các bình luận văn bản của video Brian Beckman mà ai đó đề cập ở trên . Nó hoàn toàn rõ ràng và sáng sủa đến nỗi tôi đã quyết định đăng nó trực tiếp tại đây.

Vì nhận xét này, tôi không chỉ cảm thấy mình hiểu chính xác Monads là gì mà tôi nhận ra rằng tôi thực sự đã viết một số thứ trong C # đó Monads, hoặc ít nhất là rất gần gũi, và cố gắng giải quyết các vấn đề tương tự.

Vì vậy, đây là nhận xét - đây là tất cả trích dẫn trực tiếp từ nhận xét ở đây của sylvan :

Cái này hay đấy. Đó 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ì các đơn vị đã 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à để 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, nơi IMO, các đơn vị thực sự bắt đầu trở nên hữu ích.

Được rồi, vì vậy một trong những Monads dễ nhất được gọi là "Có thể là monad" trong Haskell. Trong C #, loại Có thể được gọi Nullable<T>. Về cơ bản, đây là một lớp nhỏ chỉ gói gọn khái niệm 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ó bên trong một đơn nguyên để kết hợp các giá trị của loại này là khái niệm về sự thất bại. Tức là chúng tôi muốn có thể xem xét nhiều giá trị nullable và trả vềnull ngay khi bất kỳ một trong số chúng là null. Điều này có thể hữu ích nếu bạn, ví dụ, tìm kiếm nhiều khóa trong từ điển hoặc thứ 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 nullcho tất cả mọi thứ. Sẽ rất tẻ nhạt khi phải kiểm tra thủ công từng lần tra cứu nullvà trả lại, vì vậy chúng tôi có thể ẩn kiểm tra này bên trong toán tử liên kết (đó là điểm của các đơn vị, chúng tôi ẩn việc giữ sách trong toán tử liên kết giúp mã dễ dàng hơn sử dụng vì chúng ta có thể quên các chi tiết).

Đây là chương trình thúc đẩy toàn bộ sự việc (Tôi sẽ xác định Bindsau, đây chỉ là để cho bạn thấy lý do tại sao nó tốt đẹ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 một lúc rằng đã có hỗ trợ để thực hiện việc này Nullabletrong 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 null). Hãy giả vờ rằng không có tính năng này và đó chỉ là một lớp do 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 Bindhàm để liên kết một biến với nội dung của Nullablegiá trị của chúng ta và sau đó giả vờ 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 quấn kết quả trong một nullable ở cuối, và nullable mà một trong hai sẽ được null (nếu bất kỳ f, ghoặc hlợi nhuận null) hoặc nó sẽ là kết quả của cách tổng hợp f, ghcùng với nhau. (điều này tương tự với cách chúng ta có thể liên kết một hàng trong cơ sở dữ liệu với một biến trong LINQ và thực hiện công cụ với nó, an toàn với kiến ​​thức rằngBind toán tử sẽ đảm bảo rằng biến sẽ chỉ được truyền các giá trị hàng hợp lệ).

Bạn có thể chơi với cái này và thay đổi bất kỳ f,ghđể trở về null và bạn sẽ thấy rằng toàn bộ điều sẽ trả về null.

Vì vậy, rõ ràng toán tử liên kết phải thực hiện việc kiểm tra này cho chúng tôi và bảo lãnh trả về null nếu nó gặp giá trị null và nếu không thì chuyển qua giá trị bên trong Nullable cấu trúc vào lambda.

Đây là Bindtoán tử:

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 giống như trong video. Nó cần một M a ( Nullable<A>cú pháp C # cho trường hợp này) và một hàm từ ađến M b( Func<A, Nullable<B>>trong cú pháp C #) và nó trả về mộtM b ( Nullable<B>).

Mã chỉ đơn giản kiểm tra xem nullable có chứa một giá trị hay không và nếu nó trích xuất nó và chuyển nó vào hàm, thì nó chỉ trả về null. Điều này có nghĩa là Bindtoán tử 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 ta gọi Bindlà không có giá trị thì giá trị đó sẽ được "chuyển qua" cho hàm lambda, nếu không chúng ta sẽ bảo lãnh sớm và toàn bộ biểu thức là null. Điều này cho phép mã mà chúng ta 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 null này, chúng ta chỉ cần sử dụng Bindvà nhận một biến bị ràng buộc với giá trị bên trong giá trị đơn nguyên ( fval, gvalhvaltrong các mã ví dụ) và chúng ta có thể sử dụng chúng an toàn trong kiến ​​thức Bindsẽ chăm sóc kiểm tra chúng vô giá trị trước khi đưa chúng đi cùng.

Có những 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 Bindtoán tử chăm sóc một luồng ký tự đầu vào và sử dụng nó để viết các tổ hợp trình phân tích cú pháp. Mỗi trình kết hợp trình 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 ngược, lỗi trình phân tích cú pháp, v.v. 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 khi biết rằng việc triển khai thông minh sẽ Bindloại bỏ mọi logic đằng sau bit khó. Sau đó, có thể ai đó thêm đăng nhập vào đơn nguyên, nhưng mã sử dụng đơn vị không thay đổi, vì tất cả các phép thuật xảy ra trong định nghĩa củaBind toán tử, phần còn lại của mã không thay đổi.

Cuối cùng, đây là việc thực hiện cùng một mã trong Haskell ( -- bắt đầu một dòng bình luận).

-- 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 doký 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ự đây là do thiết kế. Monads có thể được sử dụng để gói gọn tất cả những thứ hữu ích trong lập trình mệnh lệnh (trạng thái có thể thay đổi, IO, v.v.) và được sử dụng bằng cú pháp giống như mệnh lệnh tốt đẹp này, nhưng đằng sau màn cửa, tất cả chỉ là đơn nguyên và triển khai thông minh của toán tử liên kết! Điều thú vị là bạn có thể thực hiện các đơn nguyên của riêng mình bằng cách thực hiện >>=return. Và nếu bạn làm như vậy, các đơn nguyên đó cũng sẽ có thể sử dụng doký hiệu, điều đó có nghĩa là về cơ bản bạn có thể viết các ngôn ngữ nhỏ của riêng mình chỉ bằng cách xác định hai chức năng!


3
Cá nhân tôi thích phiên bản đơn nguyên của F #, nhưng trong cả hai trường hợp chúng đều tuyệt vời.
ChaosPandion

3
Cảm ơn bạn đã quay trở lại đây và cập nhật bài viết của bạn. Những phần tiếp theo như thế này giúp các lập trình viên đang tìm kiếm trong một khu vực cụ thể thực sự hiểu cách các lập trình viên cuối cùng quan tâm đến khu vực nói trên, thay vì chỉ đơn giản là "làm thế nào để tôi làm x công nghệ y". Bạn da người!
kappasims

Về cơ bản, tôi đã đi theo con đường mà bạn đã đến và đến cùng một nơi để hiểu các đơn nguyên, cho biết đây là lời giải thích tốt nhất về hành vi ràng buộc của một đơn vị mà tôi từng thấy đối với một nhà phát triển mệnh lệnh. Mặc dù tôi nghĩ rằng bạn không hoàn toàn chạm vào mọi thứ về các đơn nguyên được giải thích thêm một chút ở trên bởi sth.
Jimmy Hoffa

@Jimmy Hoffa - không còn nghi ngờ gì nữa, bạn đã đúng. Tôi nghĩ rằng để thực sự hiểu họ sâu sắc hơn, cách tốt nhất là bắt đầu sử dụng chúng thật nhiều và rút kinh nghiệm . Tôi chưa có cơ hội đó, nhưng tôi hy vọng sẽ sớm thôi.
Charlie Hoa

Có vẻ như monad chỉ là một mức độ trừu tượng cao hơn về mặt lập trình, hoặc nó chỉ là một định nghĩa hàm liên tục và không khác biệt trong toán học. Dù bằng cách nào, chúng không phải là khái niệm mới, đặc biệt là trong toán học.
liang

11

Một đơn nguyên về cơ bản là hoãn xử lý. Nếu bạn đang cố gắng viết mã có tác dụng phụ (ví dụ I / O) bằng ngôn ngữ không cho phép chúng và chỉ cho phép tính toán thuần túy, một cách né tránh là nói: "Ok, tôi biết bạn sẽ không làm tác dụng phụ cho tôi, nhưng bạn có thể vui lòng tính toán chuyện gì sẽ xảy ra nếu bạn làm thế không? "

Đó là loại gian lận.

Bây giờ, lời giải thích đó sẽ giúp bạn hiểu ý định bức tranh lớn của các đơn vị, nhưng ma quỷ nằm trong các chi tiết. Chính xác làm thế nào bạn tính toán hậu quả? Đôi khi, nó không đẹp.

Cách tốt nhất để đưa ra một cái nhìn tổng quan về cách mà ai đó đã sử dụng để lập trình mệnh lệnh là nói rằng nó đưa bạn vào một DSL trong đó các hoạt động trông giống như những gì bạn đã sử dụng bên ngoài đơn nguyên được sử dụng để xây dựng một chức năng sẽ làm những gì bạn muốn nếu bạn có thể (ví dụ) ghi vào một tệp đầu ra. Hầu như (nhưng không thực sự) như thể bạn đang xây dựng mã trong một chuỗi để sau này được đánh giá cao.


1
Giống như trong cuốn sách I Robot? Trường hợp nhà khoa học yêu cầu một máy tính để tính toán du hành không gian và yêu cầu họ bỏ qua các quy tắc nhất định? :) :) :) :)
OscarRyz

3
Hmm, A Monad có thể được sử dụng để xử lý hoãn lại và đóng gói các chức năng tác dụng phụ, thực sự đó là ứng dụng thực sự đầu tiên trong Haskell, nhưng thực sự nó là một mẫu chung chung hơn nhiều. Sử dụng phổ biến khác bao gồm xử lý lỗi và quản lý nhà nước. Đường cú pháp (làm trong Haskell, Biểu thức tính toán trong F #, cú pháp Linq trong C #) chỉ là điều đó và cơ bản cho Monads như vậy.
Mike Hadlow

@MikeHadlow: Các trường hợp đơn lẻ để xử lý lỗi ( MaybeEither e) và quản lý nhà nước ( State s, ST s) tấn công tôi như các trường hợp cụ thể của "Vui lòng tính toán những gì sẽ xảy ra nếu bạn làm [tác dụng phụ cho tôi]". Một ví dụ khác sẽ là nondeterminism ( []).
Pyon

Điều này hoàn toàn chính xác ; với một (tốt, hai) bổ sung rằng đó là E DSL, tức là DSL được nhúng , vì mỗi giá trị "đơn âm" là một giá trị hợp lệ của chính ngôn ngữ "thuần túy" của bạn, đại diện cho một "tính toán" không có khả năng. Hơn nữa, một monadic "ràng buộc" cấu trúc tồn tại trong ngôn ngữ thuần túy của bạn cho phép bạn chuỗi tinh khiết nhà xây dựng các giá trị như vậy mà mỗi sẽ được gọi với kết quả từ tính toán trước của nó, khi toàn bộ tính toán kết hợp được "chạy". Điều này có nghĩa là chúng tôi có khả năng phân nhánh dựa trên các kết quả trong tương lai (hoặc trong mọi trường hợp, của dòng thời gian "chạy" riêng biệt).
Will Ness

nhưng đối với một lập trình viên, điều đó có nghĩa là chúng ta có thể lập trình trong EDSL trong khi trộn nó với các tính toán thuần túy của ngôn ngữ thuần túy của chúng ta. một chồng bánh sandwich nhiều lớp là một bánh sandwich nhiều lớp. nó là rằng đơn giản.
Will Ness

4

Tôi chắc chắn rằng những người dùng khác sẽ đăng sâu, nhưng tôi thấy video này hữu ích ở một mức độ nào đó, nhưng tôi sẽ nói rằng tôi vẫn chưa đến mức lưu loát với khái niệm mà tôi có thể (hoặc nên) bắt đầu giải quyết vấn đề trực giác với Monads.


1
Điều tôi thấy hữu ích hơn nữa là bình luận chứa ví dụ C # bên dưới video.
jalf

Tôi không biết nhiều về sự hữu ích, nhưng nó chắc chắn đưa các ý tưởng vào thực tế.
TheMissingLINEQ

0

Bạn có thể nghĩ về một đơn nguyên như một C # interfacemà các lớp phải thực hiện . Đây là một câu trả lời thực dụng mà bỏ qua tất cả các toán học lý thuyết thể loại đằng sau lý do tại sao bạn muốn chọn có các khai báo này trong giao diện của mình và bỏ qua tất cả các lý do tại sao bạn muốn có các đơn vị trong ngôn ngữ cố gắng tránh các tác dụng phụ, nhưng tôi thấy đó là một khởi đầu tốt khi một người hiểu giao diện (C #).


Bạn có thể xây dựng? Điều gì về một giao diện liên quan đến các đơn nguyên?
Joel Coehoorn

2
Tôi nghĩ rằng bài viết trên blog dành nhiều đoạn dành cho câu hỏi đó.
hao

0

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

Nó bắt đầu với một ví dụ động lực, hoạt động thông qua ví dụ, lấy ra một ví dụ về một đơ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 chức năng và nó sử dụng mã giả với function(argument) := expression cú 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 vị mã giả. (Để tham khảo: Mlà hàm tạo kiểu, feedlà thao tác "liên kết" và wraplà thao tác "trả về".)

using System.IO;
using System;

class Program
{
    public class M<A>
    {
        public A val;
        public string messages;
    }

    public static M<B> feed<A, B>(Func<A, M<B>> f, M<A> x)
    {
        M<B> m = f(x.val);
        m.messages = x.messages + m.messages;
        return m;
    }

    public static M<A> wrap<A>(A x)
    {
        M<A> m = new M<A>();
        m.val = x;
        m.messages = "";
        return m;
    }

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

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

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

    static void Main()
    {
        V x = new V();
        M<T> m = feed<U, T>(f, feed(g, wrap<V>(x)));
        Console.Write(m.messages);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.