Bu soruyu yayınlamamdan bu yana bir yıl geçti. Gönderdikten sonra Haskell'i birkaç aylığına araştırdım. Çok keyif aldım, ama Monad'ları araştırmaya hazır olduğum için kenara koydum. İşe geri döndüm ve projemin gerektirdiği teknolojilere odaklandım.
Bu çok havalı. Yine de biraz soyut. Gerçek örneklerin yokluğu nedeniyle hangi monadların zaten karıştığını bilmeyen insanları hayal edebiliyorum.
Bu yüzden uymaya çalışmama izin verin ve gerçekten net olmak için, çirkin görünmesine rağmen C # 'da bir örnek yapacağım. Sonunda eşdeğer Haskell'i ekleyeceğim ve size IMO, monad'ların gerçekten işe yaramaya başladığı serin Haskell sözdizimsel şekeri göstereceğim.
Tamam, bu yüzden en kolay Monad'lardan birine Haskell'deki "Belki monad" denir. C # 'da Belki tipi çağrılır Nullable<T>
. Temel olarak, geçerli ve bir değeri olan veya "boş" olan ve değeri olmayan bir değer kavramını içine alan küçük bir sınıftır.
Bu tipteki değerleri birleştirmek için bir monadın içine yapışmak için yararlı bir şey, başarısızlık kavramıdır. Yani, birden çok boş değerlere bakıp null
bunlardan herhangi biri boş olur olmaz geri dönmek istiyoruz . Örneğin, bir sözlükte veya başka bir şeyde çok sayıda anahtar ararsanız ve sonunda tüm sonuçları işlemek ve bunları bir şekilde birleştirmek istiyorsanız, ancak anahtarlardan herhangi biri sözlükte değilse, null
her şey için geri dönmek istiyorsun . Her aramayı manuel olarak kontrol etmek null
ve geri dönmek zorunda kalmak sıkıcı olacaktır
, bu nedenle bu kontrolü bağlama operatörünün içinde gizleyebiliriz (bu bir tür monadların noktasıdır, kod operatörünü daha kolay hale getiren bağlama operatörüne kitap tutmayı gizleriz ayrıntıları unutabileceğimiz için kullanın).
İşte her şeyi motive eden program ( Bind
daha sonra tanımlayacağım
, bu sadece neden güzel olduğunu göstermek için).
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();
}
}
Şimdi, bir an için bunu zaten Nullable
C # için destek olduğunu göz ardı edin (nullable ints'ı birlikte ekleyebilir ve her ikisi de null olursa null alabilirsiniz). Diyelim ki böyle bir özellik yok ve sadece özel bir sihri olmayan kullanıcı tanımlı bir sınıf. Mesele şu ki, Bind
bir değişkeni Nullable
değerimizin içeriğine bağlamak için işlevi kullanabilir ve sonra garip bir şey olmadığını iddia edebiliriz ve bunları normal ints gibi kullanabiliriz ve birlikte ekleyebiliriz. Biz sonunda null sonucu sarın ve bu null ya (eğer herhangi boş olacak f
, g
ya da h
döner null) veya toplanmasının sonucu olacak f
, g
veh
birlikte. (bu, bir veritabanındaki bir satırı LINQ'daki bir değişkene nasıl bağlayabileceğimize benzer ve Bind
operatörün değişkenin yalnızca geçerli satır değerlerinden geçirileceğinden emin olacağı bilgisiyle güvenlidir .
Buna oynamak ve herhangi değiştirebilir f
, g
ve h
return null etmek ve her şey return null göreceksiniz.
Bu nedenle açıkça, bağlayıcı operatörün bu kontrolü bizim için yapması ve bir null değerle karşılaşması durumunda null döndürmesini kurtarması ve aksi takdirde Nullable
yapının içindeki değer boyunca lambda'ya geçmesi gerekir .
İşte Bind
operatör:
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;
}
Buradaki türler videodaki gibidir. Bir sürer M a
( Nullable<A>
bu durum için C # sözdizimi) ve bir işlev a
için
M b
( Func<A, Nullable<B>>
C # sözdizimi) ve bir döner M b
( Nullable<B>
).
Kod, null değerinin bir değer içerip içermediğini kontrol eder ve eğer öyleyse onu ayıklar ve işleve iletir, aksi takdirde null değerini döndürür. Bu, Bind
operatörün bizim için tüm boş kontrol mantığını işleyeceği anlamına gelir . Yalnızca çağırdığımız değer Bind
null değilse,
bu değer lambda işlevine "iletilir", aksi takdirde erken kurtarırız ve tüm ifade null olur. Bu bizim, biz sadece kullanmak bu boş denetimi davranışının tamamen özgür olmak monad kullanan yazma o kodu verir Bind
(ve monadic değere içindeki değere bağlı bir değişken olsun fval
,
gval
ve hval
örnek kodda) ve onlara güvenli kullanabilirsiniz Bind
onları geçmeden önce null olup olmadığını kontrol etmeye özen gösterecek bilgi .
Bir monad ile yapabileceğiniz şeylere başka örnekler de var. Örneğin, Bind
operatörün bir girdi karakter akışıyla ilgilenmesini sağlayabilir ve ayrıştırıcı birleştiricileri yazmak için kullanabilirsiniz. Her ayrıştırıcı birleştirici, daha sonra geri izleme, ayrıştırıcı hataları vb Bind
. zor bitler. Daha sonra belki biri monad'a günlük ekler, ancak monad'ı kullanan kod değişmez, çünkü tüm sihir Bind
operatörün tanımında gerçekleşir, kodun geri kalanı değişmez.
Son olarak, Haskell'de aynı kodun uygulanması ( --
bir yorum satırına başlar).
-- 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)
Gördüğünüz gibi do
sonunda güzel notasyon düz zorunlu kod gibi görünmesini sağlar. Ve aslında bu tasarım gereğidir. Monadlar, tüm yararlı şeyleri zorunlu programlamada (değiştirilebilir durum, IO vb.) Kapsüllemek için kullanılabilir ve bu güzel zorunlu benzeri sözdizimini kullanarak kullanılabilir, ancak perdelerin arkasında, hepsi sadece monadlar ve bağlama operatörünün akıllı bir uygulamasıdır! Serin şey uygulayarak kendi monads uygulamak olmasıdır >>=
ve return
. Ve bunu yaparsanız, bu monadlar do
gösterimi de kullanabilecektir , bu da sadece iki işlevi tanımlayarak kendi küçük dillerinizi yazabileceğiniz anlamına gelir!