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.
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 null
dapat 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 null
untuk semuanya. Akan sangat membosankan untuk secara manual harus memeriksa setiap pencarian
null
dan 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
Bind
nanti, 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 Nullable
dalam 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 Bind
fungsi untuk mengikat variabel ke konten Nullable
nilai 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
, g
atau h
mengembalikan null) atau itu akan menjadi hasil penjumlahan f
,g
danh
bersama. (Ini analog dengan bagaimana kita dapat mengikat baris dalam database ke variabel di LINQ, dan melakukan hal-hal dengan itu, aman dengan pengetahuan bahwa Bind
operator akan memastikan bahwa variabel hanya akan pernah melewati nilai baris yang valid).
Anda dapat bermain dengan ini dan mengubah salah satu dari f
,, g
dan h
mengembalikan 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 Nullable
struktur ke dalam lambda.
Inilah Bind
operatornya:
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 a
ke
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 Bind
operator akan menangani semua logika pemeriksaan-nol untuk kami. Jika dan hanya jika nilai yang kita panggil
Bind
adalah 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 Bind
dan mendapatkan variabel terikat ke nilai di dalam nilai monadik ( fval
,
gval
dan hval
dalam kode contoh) dan kita dapat menggunakannya dengan aman dalam pengetahuan yang Bind
akan mengurus memeriksa mereka untuk nol sebelum meneruskannya.
Ada contoh lain yang bisa Anda lakukan dengan monad. Misalnya, Anda dapat membuat Bind
operator 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 Bind
memilah 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 Bind
operator, 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 do
notasi 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 do
notasi, yang berarti Anda pada dasarnya dapat menulis bahasa kecil Anda sendiri dengan hanya mendefinisikan dua fungsi!