Dalam C #, Apa itu monad?

190

Ada banyak pembicaraan tentang monad hari ini. Saya telah membaca beberapa artikel / posting blog, tetapi saya tidak bisa cukup jauh dengan contoh-contoh mereka untuk memahami konsep sepenuhnya. Alasannya adalah bahwa monad adalah konsep bahasa fungsional, dan dengan demikian contohnya adalah dalam bahasa yang belum pernah saya gunakan (karena saya belum menggunakan bahasa fungsional secara mendalam). Saya tidak dapat memahami sintaksis cukup dalam untuk mengikuti artikel sepenuhnya ... tapi saya bisa mengatakan ada sesuatu yang layak dipahami di sana.

Namun, saya tahu C # cukup baik, termasuk ekspresi lambda dan fitur fungsional lainnya. Saya tahu C # hanya memiliki subset fitur fungsional, dan jadi mungkin monad tidak dapat diekspresikan dalam C #.

Namun, mungkinkah untuk menyampaikan konsep tersebut? Setidaknya saya harap begitu. Mungkin Anda dapat menyajikan contoh C # sebagai sebuah yayasan, dan kemudian menggambarkan apa yang ingin dilakukan oleh pengembang C # dari sana tetapi tidak bisa karena bahasa tersebut tidak memiliki fitur pemrograman fungsional. Ini akan fantastis, karena akan menyampaikan maksud dan manfaat monad. Jadi, inilah pertanyaan saya: Apa penjelasan terbaik yang bisa Anda berikan tentang monad kepada pengembang C # 3?

Terima kasih!

(EDIT: Ngomong-ngomong, saya tahu setidaknya sudah ada 3 pertanyaan "apa itu monad" di SO. Namun, saya menghadapi masalah yang sama dengan mereka ... jadi pertanyaan ini sangat dibutuhkan, karena C # -developer fokus. Terima kasih.)

Charlie Flowers
sumber
Harap dicatat bahwa ini sebenarnya adalah pengembang C # 3.0. Jangan salah dengan .NET 3.5. Selain itu, pertanyaan yang bagus.
Razzie
4
Itu worth menunjukkan bahwa ekspresi LINQ permintaan adalah contoh perilaku monadik di C # 3.
Erik Forbes
1
Saya masih berpikir itu adalah pertanyaan rangkap. Salah satu jawaban di stackoverflow.com/questions/2366/can-anyone-explain-monads tautan ke channel9vip.orcsweb.com/shows/Going+Deep/… , di mana salah satu komentar memiliki contoh C # yang sangat bagus. :)
jalf
4
Namun, itu hanya satu tautan dari satu jawaban ke salah satu pertanyaan SO. Saya melihat nilai dalam pertanyaan yang difokuskan pada pengembang C #. Ini adalah sesuatu yang saya akan tanyakan kepada programmer fungsional yang dulu melakukan C # jika saya tahu, jadi sepertinya masuk akal untuk menanyakannya pada SO. Tapi saya menghormati hak Anda atas pendapat Anda juga.
Charlie Flowers
1
Bukankah hanya satu jawaban yang Anda butuhkan? ;) Maksud saya adalah hanya bahwa salah satu pertanyaan lain (dan sekarang ini juga, jadi yay) memang memiliki jawaban spesifik C # (yang tampaknya ditulis dengan sangat baik, sebenarnya. Mungkin penjelasan terbaik yang pernah saya lihat)
jalf

Jawaban:

147

Sebagian besar yang Anda lakukan dalam pemrograman sepanjang hari adalah menggabungkan beberapa fungsi bersama untuk membangun fungsi yang lebih besar darinya. Biasanya Anda tidak hanya memiliki fungsi di kotak peralatan Anda, tetapi juga hal-hal lain seperti operator, penugasan variabel, dan sejenisnya, tetapi umumnya program Anda menggabungkan banyak "perhitungan" dengan perhitungan yang lebih besar yang akan digabungkan bersama lebih lanjut.

Monad adalah beberapa cara untuk melakukan ini "menggabungkan perhitungan".

Biasanya "operator" Anda yang paling dasar untuk menggabungkan dua perhitungan bersama adalah ;:

a; b

Ketika Anda mengatakan ini, Anda maksudkan "lakukan dulu a, baru lakukan b". Hasilnya a; bpada dasarnya lagi perhitungan yang dapat digabungkan dengan lebih banyak barang. Ini adalah monad sederhana, ini adalah cara menyisir perhitungan kecil ke yang lebih besar. The ;mengatakan "melakukan hal yang di sebelah kiri, kemudian melakukan hal yang di sebelah kanan".

Hal lain yang dapat dilihat sebagai monad dalam bahasa berorientasi objek adalah .. Seringkali Anda menemukan hal-hal seperti ini:

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

Pada .dasarnya berarti "mengevaluasi perhitungan di sebelah kiri, dan kemudian memanggil metode di sebelah kanan pada hasil itu". Ini adalah cara lain untuk menggabungkan fungsi / komputasi bersama, sedikit lebih rumit daripada ;. Dan konsep chaining things bersama .adalah monad, karena itu adalah cara menggabungkan dua komputasi bersama-sama ke komputasi baru.

Monad lain yang cukup umum, yang tidak memiliki sintaks khusus, adalah pola ini:

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;

Nilai balik -1 menunjukkan kegagalan, tetapi tidak ada cara nyata untuk mengabstraksi pengecekan kesalahan ini, bahkan jika Anda memiliki banyak panggilan API yang perlu Anda gabungkan dengan cara ini. Ini pada dasarnya hanyalah monad lain yang menggabungkan panggilan fungsi dengan aturan "jika fungsi di sebelah kiri mengembalikan -1, lakukan return -1 sendiri, jika tidak panggil fungsi di sebelah kanan". Jika kami memiliki operator >>=yang melakukan hal ini, kami cukup menulis:

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

Itu akan membuat hal-hal lebih mudah dibaca dan membantu untuk mengabstraksi cara khusus kami menggabungkan fungsi, sehingga kita tidak perlu mengulangi diri kita lagi dan lagi.

Dan ada banyak lagi cara untuk menggabungkan fungsi / perhitungan yang berguna sebagai pola umum dan dapat diabstraksi dalam monad, memungkinkan pengguna monad untuk menulis kode yang jauh lebih ringkas dan jelas, karena semua pembukuan dan pengelolaan fungsi yang digunakan dilakukan di monad.

Sebagai contoh, hal di atas >>=dapat diperluas untuk "melakukan pemeriksaan kesalahan dan kemudian memanggil sisi kanan pada soket yang kita dapatkan sebagai input", sehingga kita tidak perlu secara eksplisit menentukan socketbanyak kali:

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

Definisi formal sedikit lebih rumit karena Anda harus khawatir tentang cara mendapatkan hasil dari satu fungsi sebagai input ke yang berikutnya, jika fungsi itu membutuhkan input itu dan karena Anda ingin memastikan bahwa fungsi yang Anda gabungkan cocok dengan cara Anda mencoba menggabungkan mereka di monad Anda. Tetapi konsep dasarnya adalah hanya Anda memformalkan berbagai cara untuk menggabungkan fungsi bersama.

sth
sumber
28
Jawaban bagus! Saya akan memberikan kutipan oleh Oliver Steele, mencoba menghubungkan Monads dengan operator yang berlebihan à la C ++ atau C #: Monads memungkinkan Anda membebani ';' operator.
Jörg W Mittag
6
@ JörgWMittag Saya membaca kutipan itu sebelumnya, tapi itu terdengar seperti omong kosong yang terlalu memabukkan. Sekarang saya mengerti monad dan membaca penjelasan tentang bagaimana ';' adalah satu, saya mengerti. Tapi saya pikir itu benar-benar pernyataan yang tidak masuk akal bagi kebanyakan pengembang imperatif. ';' tidak dilihat sebagai operator lebih dari // bagi kebanyakan orang.
Jimmy Hoffa
2
Apakah Anda yakin tahu apa itu monad? Monads bukan "fungsi" atau perhitungan, ada aturan untuk monads.
Luis
Dalam ;contoh Anda : Objek / tipe data apa yang ;dipetakan? (Pikirkan Listpeta Tuntuk List<T>) Bagaimana ;peta morfisme / fungsi antara objek / tipe data? Apa pure, join, binduntuk ;?
Micha Wiedenmann
44

Sudah setahun sejak saya memposting pertanyaan ini. Setelah mempostingnya, saya menyelidiki Haskell selama beberapa bulan. Saya sangat menikmatinya, tetapi saya mengesampingkannya ketika saya siap untuk mempelajari Monads. Saya kembali bekerja dan fokus pada teknologi yang dibutuhkan proyek saya.

Dan tadi malam, saya datang dan membaca kembali tanggapan-tanggapan ini. Yang paling penting , saya membaca kembali contoh C # spesifik dalam komentar teks dari video Brian Beckman yang disebutkan seseorang di atas . Itu sangat jelas dan menerangi sehingga saya memutuskan untuk mempostingnya langsung di sini.

Karena komentar ini, tidak hanya aku merasa seperti aku mengerti persis apa yang monads yang ... Saya menyadari saya sudah benar-benar menulis beberapa hal di C # yang merupakan monads ... atau setidaknya sangat dekat, dan berusaha untuk memecahkan masalah yang sama.

Jadi, inilah komentar - ini semua kutipan langsung dari komentar di sini oleh sylvan :

Ini keren sekali. Ini agak abstrak sekalipun. Saya bisa membayangkan orang-orang yang tidak tahu monad apa yang sudah menjadi bingung karena kurangnya contoh nyata.

Jadi izinkan saya mencoba untuk mematuhi, dan hanya untuk benar-benar jelas saya akan melakukan contoh dalam C #, meskipun itu akan terlihat jelek. Saya akan menambahkan Haskell yang setara di bagian akhir dan menunjukkan kepada Anda gula sintaksis Haskell yang keren di mana, IMO, monad benar-benar mulai menjadi berguna.

Oke, jadi salah satu Monads termudah disebut "Maybe monad" di Haskell. Dalam C # tipe Mungkin disebut Nullable<T>. Ini pada dasarnya kelas kecil yang hanya merangkum konsep nilai yang valid dan memiliki nilai, atau "null" dan tidak memiliki nilai.

Suatu hal yang berguna untuk menempel di dalam monad untuk menggabungkan nilai-nilai jenis ini adalah gagasan kegagalan. Yaitu kami ingin dapat melihat beberapa nilai yang nulldapat dibatalkan dan kembali segera setelah salah satu dari mereka bernilai nol. Ini bisa berguna jika Anda, misalnya, mencari banyak kunci dalam kamus atau sesuatu, dan pada akhirnya Anda ingin memproses semua hasil dan menggabungkannya entah bagaimana, tetapi jika ada kunci yang tidak ada dalam kamus, Anda ingin kembali nulluntuk semuanya. Akan sangat membosankan untuk secara manual harus memeriksa setiap pencarian nulldan kembali, sehingga kita dapat menyembunyikan pemeriksaan ini di dalam operator bind (yang merupakan titik monad, kita menyembunyikan pembukuan di operator bind yang membuat kode lebih mudah untuk gunakan karena kita bisa melupakan detailnya).

Inilah program yang memotivasi semuanya (saya akan jelaskan Bindnanti, ini hanya untuk menunjukkan kepada Anda mengapa itu bagus).

 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();
        }
    }

Sekarang, abaikan sejenak bahwa sudah ada dukungan untuk melakukan ini Nullabledalam C # (Anda dapat menambahkan int nullable bersama-sama dan Anda mendapatkan null jika salah satu adalah null). Mari kita berpura-pura tidak ada fitur seperti itu, dan itu hanya kelas yang ditentukan pengguna tanpa sihir khusus. Intinya adalah bahwa kita dapat menggunakan Bindfungsi untuk mengikat variabel ke konten Nullablenilai kita dan kemudian berpura-pura bahwa tidak ada yang aneh terjadi, dan menggunakannya seperti int normal dan hanya menambahkannya bersama. Kami membungkus hasilnya dalam nullable di akhir, dan nullable itu akan menjadi nol (jika ada f, gatau hmengembalikan null) atau itu akan menjadi hasil penjumlahan f,g danhbersama. (Ini analog dengan bagaimana kita dapat mengikat baris dalam database ke variabel di LINQ, dan melakukan hal-hal dengan itu, aman dengan pengetahuan bahwa Bindoperator akan memastikan bahwa variabel hanya akan pernah melewati nilai baris yang valid).

Anda dapat bermain dengan ini dan mengubah salah satu dari f,, gdan hmengembalikan nol dan Anda akan melihat bahwa semuanya akan kembali nol.

Jadi jelas operator bind harus melakukan pengecekan ini untuk kami, dan memberikan pengembalian null jika menemui nilai nol, dan jika tidak meneruskan nilai di dalam Nullablestruktur ke dalam lambda.

Inilah Bindoperatornya:

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;
}

Jenis-jenis di sini sama seperti dalam video. Dibutuhkan M a ( Nullable<A>dalam sintaks C # untuk kasus ini), dan fungsi dari ake M b( Func<A, Nullable<B>>dalam sintaks C #), dan mengembalikan M b ( Nullable<B>).

Kode hanya memeriksa apakah nullable berisi nilai dan jika demikian mengekstraknya dan meneruskannya ke fungsi, kalau tidak itu hanya mengembalikan nol. Ini berarti bahwa Bindoperator akan menangani semua logika pemeriksaan-nol untuk kami. Jika dan hanya jika nilai yang kita panggil Bindadalah non-nol maka nilai itu akan "diteruskan" ke fungsi lambda, kalau tidak kita bail out lebih awal dan seluruh ekspresi adalah nol. Ini memungkinkan kode yang kita tulis menggunakan monad untuk sepenuhnya bebas dari perilaku pemeriksaan-nol ini, kita hanya menggunakan Binddan mendapatkan variabel terikat ke nilai di dalam nilai monadik ( fval, gvaldan hvaldalam kode contoh) dan kita dapat menggunakannya dengan aman dalam pengetahuan yang Bindakan mengurus memeriksa mereka untuk nol sebelum meneruskannya.

Ada contoh lain yang bisa Anda lakukan dengan monad. Misalnya, Anda dapat membuat Bindoperator menangani aliran input karakter, dan menggunakannya untuk menulis pengurai parser. Setiap combinator parser kemudian dapat sepenuhnya tidak menyadari hal-hal seperti pelacakan kembali, kegagalan parser dll., Dan hanya menggabungkan parser kecil bersama-sama seolah-olah hal-hal tidak akan salah, aman dalam pengetahuan bahwa implementasi cerdas Bindmemilah semua logika di balik bit yang sulit. Kemudian nanti mungkin seseorang menambahkan logging ke monad, tetapi kode menggunakan monad tidak berubah, karena semua keajaiban terjadi dalam definisi Bindoperator, sisa kode tidak berubah.

Akhirnya, inilah penerapan kode yang sama di Haskell ( -- memulai baris komentar).

-- 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)

Seperti yang Anda lihat donotasi bagus di akhir membuatnya terlihat seperti kode imperatif langsung. Dan memang ini adalah desain. Monads dapat digunakan untuk merangkum semua hal yang berguna dalam pemrograman imperatif (keadaan dapat berubah, IO dll.) Dan digunakan menggunakan sintaks seperti imperatif yang bagus ini, tetapi di balik tirai, itu semua hanya monad dan implementasi cerdas dari operator bind! Yang keren adalah Anda bisa mengimplementasikan monad Anda sendiri dengan menerapkan >>=dan return. Dan jika Anda melakukannya, monad-monad itu juga akan dapat menggunakan donotasi, yang berarti Anda pada dasarnya dapat menulis bahasa kecil Anda sendiri dengan hanya mendefinisikan dua fungsi!

Charlie Flowers
sumber
3
Secara pribadi saya lebih suka versi monad F #, tetapi dalam kedua kasus mereka mengagumkan.
ChaosPandion
3
Terima kasih telah kembali ke sini dan memperbarui pos Anda. Ini adalah tindak lanjut seperti ini yang membantu programmer yang melihat ke area spesifik benar-benar memahami bagaimana sesama programmer akhirnya menganggap area tersebut, bukan hanya memiliki "bagaimana saya melakukan teknologi x" untuk pergi dari. Kamu laki-laki!
kappasims
Saya telah mengambil jalur yang sama dengan yang Anda miliki pada dasarnya dan tiba di tempat yang sama untuk memahami monad, yang mengatakan ini adalah satu-satunya penjelasan terbaik tentang perilaku mengikat monad yang pernah saya lihat untuk pengembang imperatif. Meskipun saya pikir Anda tidak cukup menyentuh tentang segala sesuatu tentang monad yang sedikit lebih dijelaskan di atas oleh sth.
Jimmy Hoffa
@ Jimmy Hoffa - tidak diragukan lagi Anda benar. Saya pikir untuk benar-benar memahami mereka lebih dalam, cara terbaik adalah mulai banyak menggunakannya dan mendapatkan pengalaman . Saya belum memiliki kesempatan itu, tetapi saya berharap segera.
Charlie Flowers
Tampaknya monad hanyalah tingkat abstraksi yang lebih tinggi dalam hal pemrograman, atau itu hanya definisi fungsi yang kontinyu dan tidak dapat dibedakan dalam matematika. Either way, mereka bukan konsep baru, terutama dalam matematika.
liang
11

Monad pada dasarnya merupakan pemrosesan yang ditangguhkan. Jika Anda mencoba menulis kode yang memiliki efek samping (mis. I / O) dalam bahasa yang tidak mengizinkannya, dan hanya memungkinkan perhitungan murni, satu penghindaran adalah mengatakan, "Oke, saya tahu Anda tidak akan melakukan efek samping untuk saya, tetapi bisakah Anda menghitung apa yang akan terjadi jika Anda melakukannya? "

Ini semacam curang.

Sekarang, penjelasan itu akan membantu Anda memahami maksud gambaran besar dari monad, tetapi iblis ada dalam rinciannya. Bagaimana tepatnya cara Anda menghitung konsekuensi? Terkadang, itu tidak cantik.

Cara terbaik untuk memberikan ikhtisar tentang bagaimana seseorang yang terbiasa dengan pemrograman imperatif adalah dengan mengatakan bahwa ia menempatkan Anda dalam DSL di mana operasi yang terlihat secara sintaksis seperti apa yang Anda gunakan di luar monad digunakan sebagai gantinya untuk membangun fungsi yang akan dilakukan apa yang Anda inginkan jika Anda dapat (misalnya) menulis ke file output. Hampir (tetapi tidak benar-benar) seolah-olah Anda sedang membangun kode dalam sebuah string untuk kemudian dievaluasi.

MarkusQ
sumber
1
Seperti di buku Robot I? Di mana ilmuwan meminta komputer untuk menghitung perjalanan ruang angkasa dan meminta mereka untuk melewati aturan tertentu? :) :) :) :)
OscarRyz
3
Hmm, A Monad dapat digunakan untuk pemrosesan yang ditangguhkan dan untuk merangkum fungsi-fungsi efek samping, memang itu adalah aplikasi nyata pertama di Haskell, tetapi sebenarnya pola yang jauh lebih umum. Penggunaan umum lainnya termasuk penanganan kesalahan dan manajemen negara. Gula sintaksis (lakukan dalam Haskell, Ekspresi Komputasi dalam F #, sintaks Linq dalam C #) hanyalah itu dan mendasar bagi Monads.
Mike Hadlow
@MikeHadlow: Contoh monad untuk penanganan kesalahan ( Maybedan Either e) dan manajemen negara ( State s, ST s) menyerang saya sebagai contoh khusus "Tolong hitung apa yang akan terjadi jika Anda melakukan [efek samping untuk saya]". Contoh lain adalah nondeterminisme ( []).
pyon
ini persis tepat; dengan satu (well, dua) tambahan bahwa itu adalah E DSL, yaitu DSL yang disematkan , karena setiap nilai "monadik" adalah nilai yang valid dari bahasa "murni" Anda sendiri, berarti "komputasi" yang berpotensi tidak murni. Lebih jauh lagi, konstruksi "binding" monadik ada dalam bahasa murni Anda yang memungkinkan Anda untuk menjalin konstruktor murni dari nilai-nilai seperti itu di mana masing-masing akan dipanggil dengan hasil dari perhitungan sebelumnya, ketika seluruh gabungan perhitungan adalah "run". Ini berarti kami memiliki kemampuan untuk bercabang pada hasil yang akan datang (atau dalam hal apa pun, dari timeline "run" yang terpisah).
Will Ness
tetapi untuk seorang programmer itu berarti kita dapat memprogram dalam EDSL sambil mencampurnya dengan perhitungan murni dari bahasa murni kita. setumpuk sandwich berlapis-lapis adalah sandwich berlapis-lapis. itu adalah bahwa sederhana.
Will Ness
4

Saya yakin pengguna lain akan memposting secara mendalam, tetapi saya menemukan video ini membantu sampai batas tertentu, tetapi saya akan mengatakan bahwa saya masih tidak sampai kefasihan dengan konsep sedemikian rupa sehingga saya bisa (atau harus) mulai menyelesaikan masalah secara intuitif dengan Monads.

TheMissingLINQ
sumber
1
Apa yang saya temukan lebih bermanfaat adalah komentar yang berisi contoh C # di bawah video.
jalf
Saya tidak tahu tentang lebih membantu, tetapi tentu saja mempraktikkan ide-ide.
TheMissingLINQ
0

Anda dapat menganggap monad sebagai C # interfaceyang harus diimplementasikan oleh kelas . Ini adalah jawaban pragmatis yang mengabaikan semua matematika teoretis kategori di balik mengapa Anda ingin memilih untuk memiliki deklarasi ini di antarmuka Anda dan mengabaikan semua alasan mengapa Anda ingin memiliki monad dalam bahasa yang mencoba menghindari efek samping, tapi saya menemukan ini sebagai awal yang baik sebagai seseorang yang mengerti (C #) antarmuka.

hao
sumber
Bisakah Anda menguraikan? Ada apa dengan antarmuka yang menghubungkannya dengan monads?
Joel Coehoorn
2
Saya pikir posting blog menghabiskan beberapa paragraf yang ditujukan untuk pertanyaan itu.
hao
0

Lihat jawaban saya untuk "Apa itu monad?"

Itu dimulai dengan contoh yang memotivasi, bekerja melalui contoh itu, mengambil contoh dari monad, dan secara resmi mendefinisikan "monad".

Ini mengasumsikan tidak memiliki pengetahuan pemrograman fungsional dan menggunakan pseudocode dengan function(argument) := expressionsintaks dengan ekspresi sesederhana mungkin.

Program C # ini adalah implementasi dari pseudocode monad. (Untuk referensi: Madalah konstruktor tipe, feedadalah operasi "bind", dan wrapmerupakan operasi "return".)

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);
    }
}
Yordania
sumber