Monad sade İngilizce mi? (FP arkaplanı olmayan OOP programcısı için)


743

Bir OOP programcısının (herhangi bir işlevsel programlama arka planı olmadan) anlayacağı anlamıyla, monad nedir?

Hangi sorunu çözüyor ve en yaygın kullanıldığı yerler hangileri?

DÜZENLE:

Aradığım anlayışı açıklığa kavuşturmak için, diyelim ki monadları olan bir FP uygulamasını bir OOP uygulamasına dönüştürdüğünüzü varsayalım. Monadların sorumluluklarını OOP uygulamasına taşımak için ne yapardınız?




10
@Pavel: Eric'in altında aldığımız cevap , OO geçmişi olan insanlar için (FP arka planının aksine) önerilen diğer Q'lardan çok daha iyi.
Donal Fellows

5
@Donal: Bu takdirde ise bir dupe (hangi Fikrim yok), iyi bir cevap orijinaline eklenmelidir. Yani: iyi bir cevap, kopya olarak kapanmayı engellemez. Yeterince yakın bir kopya ise, bir moderatör tarafından birleştirme olarak gerçekleştirilebilir.
dmckee --- ex-moderatör kedi yavrusu

Yanıtlar:


732

GÜNCELLEME: Bu soru Monads'ta okuyabileceğiniz son derece uzun bir blog dizisinin konusuydu - harika soru için teşekkürler!

Bir OOP programcısının (herhangi bir işlevsel programlama arka planı olmadan) anlayacağı anlamıyla, monad nedir?

Bir tek hücreli bir olan türleri "güçlendirici" belirli kurallara uyar ve Resim belirli işlemleri sahip olan .

İlk olarak, bir "tür yükselteci" nedir? Bununla, bir türü alıp daha özel bir türe dönüştürmenizi sağlayan bir sistem demek istiyorum. Örneğin, C # düşünün Nullable<T>. Bu bir tür amplifikatördür. Bir türü almanızı, söylemenizi intve bu türe yeni bir özellik eklemenizi sağlar, yani, daha önce yapamadığında boş olabilir.

İkinci bir örnek olarak düşünün IEnumerable<T>. Bir tür amplifikatördür. Bir türü almanızı, diyelim ki, stringbu türe yeni bir özellik eklemenizi sağlar, yani artık herhangi bir sayıda tek dizeden bir dizi dizisi oluşturabilirsiniz.

"Belirli kurallar" nelerdir? Kısacası, altta yatan tipteki fonksiyonların, yükseltilmiş tip üzerinde çalışması için, fonksiyonel bileşimin normal kurallarına uymaları için mantıklı bir yol olduğu. Örneğin, tamsayılarda bir fonksiyonunuz varsa,

int M(int x) { return x + N(x * 2); }

bu durumda ilgili işlev, Nullable<int>oradaki tüm operatörlerin ve çağrıların, daha önce yaptıkları gibi "aynı şekilde" birlikte çalışmasını sağlayabilir.

(Bu inanılmaz derecede belirsiz ve kesin değil; fonksiyonel kompozisyon bilgisi hakkında hiçbir şey varsaymayan bir açıklama istediniz.)

"Operasyonlar" nelerdir?

  1. Düz bir türden bir değer alan ve eşdeğer monadik değeri oluşturan bir "birim" işlemi (bazen "dönüş" işlemi olarak da adlandırılır) vardır. Bu, özünde, amplifiye edilmemiş bir tipin değerini almak ve onu amplifiye tipin bir değerine dönüştürmek için bir yol sağlar. OO dilinde bir kurucu olarak uygulanabilir.

  2. Monadik bir değer ve değeri dönüştürebilen ve yeni bir monadik değer döndüren bir işlev alan bir "bağlama" işlemi vardır. Bağlama, monad'ın anlambilimini tanımlayan temel işlemdir. Yükseltilmemiş tipteki işlemleri, daha önce bahsedilen fonksiyonel kompozisyon kurallarına uyan amplifiye tipteki operasyonlara dönüştürmemizi sağlar.

  3. Çoğaltılmamış türü çoğaltılmış türden çıkarmanın bir yolu vardır. Açıkçası bu işlemin bir monad olması gerekmez. (Bir comonad'ınız olması gerekiyorsa da . Bunları bu makalede daha fazla düşünmeyeceğiz.)

Yine Nullable<T>bir örnek alın. Yapıcı ile bir a intdönüştürebilirsiniz Nullable<int>. C # derleyicisi sizin için en boş "kaldırma" ile ilgilenir, ancak yapmadıysa, kaldırma dönüşümü basittir: bir işlem,

int M(int x) { whatever }

dönüştü

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

Ve Nullable<int>geri dönüş bir özelliği intile yapılır Value.

Anahtar bit olan fonksiyon dönüşümüdür. Sıfırlanabilir işlemin gerçek anlambiliminin - bir nullyayılma üzerindeki işlemin - nulldönüşümde nasıl yakalandığına dikkat edin. Bunu genelleştirebiliriz.

Eğer bir işlevi olduğunu varsayalım intiçin intbizim orijinali gibi M. Bunu bir intve işlevini alan bir işleve kolayca yapabilirsiniz, Nullable<int>çünkü sonucu nullable yapıcısı üzerinden çalıştırabilirsiniz. Şimdi bu yüksek dereceli yönteme sahip olduğunuzu varsayalım:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Bununla ne yapabileceğinizi görüyor musunuz? Bir alan herhangi bir yöntem intve bir döner intya da alan bir intve duruma göre bir Nullable<int>artık null semantik uygulanmış olabilir .

Ayrıca: iki yönteminiz olduğunu varsayalım

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

ve bunları oluşturmak istersiniz:

Nullable<int> Z(int s) { return X(Y(s)); }

Kendisine, Zbileşimi Xve Y. Ama bunu yapamazsınız çünkü a Xalır intve a Ydöndürür Nullable<int>. Ancak "bağlama" işlemine sahip olduğunuz için bu işlemi yapabilirsiniz:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

Bir monad üzerindeki bağlama işlemi, amplifiye tipler üzerindeki fonksiyonların kompozisyonunu çalıştıran şeydir. Yukarıda ele aldığım "kurallar", monad'ın normal fonksiyon kompozisyonunun kurallarını koruduğu; özdeşlik işlevleriyle bestelemenin özgün işlevle sonuçlanması, söz konusu bileşimin ilişkilendirici olması vb.

C # 'da "Bind", "SelectMany" olarak adlandırılır. Sekans monadında nasıl çalıştığına bir göz atın. İki şeye ihtiyacımız var: bir değeri bir diziye dönüştürmek ve dizilerdeki işlemleri bağlamak. Bir bonus olarak, "bir diziyi tekrar bir değere çevirin". Bu işlemler:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

Sıfırlanabilir monad kuralı, "nullables üreten iki işlevi bir araya getirmek, içteki null ile sonuçlanıp sonuçlanmadığını kontrol etmek; eğer olursa, null üretmek, eğer değilse, dış olanı sonuçla çağırmak" idi. Bu, istenen sıfırlanabilir anlambilimdir.

Sekans monad kuralı "sekansları birlikte üreten iki fonksiyonu bir araya getirmek, dış fonksiyonu iç fonksiyon tarafından üretilen her elemente uygulamak ve daha sonra elde edilen tüm sekansları bir araya getirmek" tir. Monadların temel semantiği Bind/ SelectManyyöntemlerinde ele alınır ; Bu monad gerçekten söyler yöntemdir demektir .

Daha iyisini yapabiliriz. Diyelim ki bir ints dizisi ve ints alan ve dize dizileriyle sonuçlanan bir yöntem var. Birinin girdileri diğerinin çıktılarıyla eşleştiği sürece, farklı amplifiye tipler alan ve geri dönen fonksiyonların kompozisyonuna izin vermek için ciltleme işlemini genelleştirebiliriz:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

Şimdi diyebiliriz ki "bu bireysel tamsayıları bir tamsayılar dizisine yükselt. dizelerin tüm dizileri. " Monadlar amplifikasyonlarınızı oluşturmanıza izin verir .

Hangi sorunu çözüyor ve en yaygın kullanıldığı yerler hangileri?

Bu daha çok "singleton modeli hangi sorunları çözüyor?"

Monadlar genellikle aşağıdaki gibi sorunları çözmek için kullanılır:

  • Bu tür için yeni yetenekler yapmam ve yeni yetenekleri kullanmak için hala bu türdeki eski işlevleri birleştirmem gerekiyor.
  • Tipler üzerinde bir sürü işlemi yakalamam ve bu işlemleri kompozit nesneler olarak temsil etmeliyim, temsil edilen doğru operasyon dizisine sahip olana kadar daha büyük ve daha büyük kompozisyonlar oluşturuyorum ve sonra şeyden sonuç almaya başlamam gerekiyor
  • Yan etki operasyonlarını, yan etkilerden nefret eden bir dilde temiz bir şekilde temsil etmem gerekiyor

C #, tasarımında monad kullanır. Daha önce de belirtildiği gibi, nullable paterni "belki monad" a benzemektedir. LINQ tamamen monadlardan oluşur; SelectManyyöntem operasyonların kompozisyonun semantik çalışır budur. (Erik Meijer, her LINQ işlevinin gerçekte uygulanabileceğine işaret etmeyi sever SelectMany; diğer her şey sadece bir kolaylıktır.)

Aradığım anlayışı açıklığa kavuşturmak için, diyelim ki monadları olan bir FP uygulamasını bir OOP uygulamasına dönüştürdüğünüzü varsayalım. Monadların sorumluluklarını OOP uygulamasına taşımak için ne yapardınız?

Çoğu OOP dili, monad modelinin kendisini doğrudan temsil edecek kadar zengin bir tip sistemine sahip değildir; genel türlerden daha yüksek türleri destekleyen bir tür sistemine ihtiyacınız vardır. Bu yüzden bunu yapmaya çalışmam. Daha ziyade, her monad'ı temsil eden genel türleri uygulayabilir ve ihtiyacınız olan üç işlemi temsil eden yöntemler uygulayabilirim: bir değeri yükseltilmiş bir değere dönüştürmek, (belki) yükseltilmiş bir değeri bir değere dönüştürmek ve bir işlevi yükseltilmemiş değerlere dönüştürmek yükseltilmiş değerler üzerinde bir fonksiyon.

Başlamak için iyi bir yer, LINQ'yu C # 'da nasıl uyguladığımızdır. Çalışma SelectManyyöntemi; dizi monadının C # 'da nasıl çalıştığını anlamanın anahtarıdır. Bu çok basit bir yöntem ama çok güçlü!


Önerilen, daha fazla okuma:

  1. C # 'daki monadların daha derinlemesine ve teorik olarak sağlam bir açıklaması için, ( Eric Lippert'in ) meslektaşım Wes Dyer'in konuyla ilgili makalesini şiddetle tavsiye ederim . Bu makale, sonunda benim için "tıkladığında" monadları bana açıkladı.
  2. Neden etrafında bir monad isteyebileceğinin iyi bir örneği ( örneklerinde Haskell kullanıyor) .
  3. Bir önceki makalenin JavaScript'e çevirisi "çeviri".


17
Bu harika bir cevap, ama kafam aspirasyona gitti. Bu haftasonu takip edip ona bakacağım ve işler yolunda gitmezse ve aklıma mantıklı geliyorsa size sorular soracağım.
Paul Nathan

5
Her zamanki gibi mükemmel bir açıklama Eric. Daha teorik (ama yine de son derece ilginç) tartışma için Bart De Smet'in MinLINQ'daki blog yazısını bazı fonksiyonel programlama yapılarını tekrar C # ile ilişkilendirmede yararlı buldum. community.bartdesmet.net/blogs/bart/archive/2010/01/01/…
Ron Warholic

41
O söylemek bana daha mantıklı arttırır ziyade türleri amplifikas onları.
Gabe

61
@slomojo: yazdım ve yazmayı düşündüğüm şeye geri değiştirdim. Siz ve Gabe kendi cevabınızı yazmak istiyorsanız, hemen ileri gidersiniz.
Eric Lippert

24
@Eric, Tabii ki size bağlı, ancak Amplifikatör mevcut özelliklerin artırıldığını ima ediyor, bu da yanıltıcı.
ocodo

341

Neden monadlara ihtiyacımız var?

  1. Sadece fonksiyonları kullanarak programlamak istiyoruz . (sonuçta "fonksiyonel programlama" -FP).
  2. Sonra, ilk büyük sorunumuz var. Bu bir programdır:

    f(x) = 2 * x

    g(x,y) = x / y

    İlk önce ne yapılacağını nasıl söyleyebiliriz ? İşlevlerden fazlasını kullanarak sıralı bir işlev dizisi (yani bir program ) nasıl oluşturabiliriz ?

    Çözüm: oluşturma işlevleri . Önce gve sonra isterseniz fsadece yazın f(g(x,y)). Tamam ama ...

  3. Diğer sorunlar: bazı işlevler başarısız olabilir (yani g(2,0), 0'a böl). Biz FP hiçbir "istisna" . Bunu nasıl çözeriz?

    Çözüm: Fonksiyonların iki tür şeyi döndürmesine izin verelim : g : Real,Real -> Real(iki realiteden gerçeğe fonksiyon) yerine, izin verelim g : Real,Real -> Real | Nothing(iki realiteden fonksiyona (gerçek veya hiçbir şey)).

  4. Ancak işlevler (daha basit olmak gerekirse) yalnızca bir şey döndürmelidir .

    Çözüm: Döndürülecek yeni bir veri türü oluşturalım, gerçek ya da basit bir şey olmayan bir " boks tipi " oluşturalım. Dolayısıyla, sahip olabiliriz g : Real,Real -> Maybe Real. Tamam ama ...

  5. Şimdi ne olacak f(g(x,y))? ftüketmeye hazır değil Maybe Real. Ve, ga tüketmek için bağlanabileceğimiz her işlevi değiştirmek istemiyoruz Maybe Real.

    Çözüm: "bağlanmak" / "oluşturmak" / "bağlantı" işlevlerini kullanmak için özel bir işleve sahip olalım . Bu şekilde, perde arkasında, bir fonksiyonun çıkışını aşağıdakini beslemek için uyarlayabiliriz.

    Bizim durumumuzda: g >>= f(connect / oluşturma giçin f). Çıktısını >>=almak g, incelemek ve Nothingsadece arama fve geri dönmemesi durumunda Nothing; veya tam tersine, kutuyu çıkarın Realve fonunla besleyin . (Bu algoritma sadece uygulamasıdır >>=için Maybetip).

  6. Aynı kalıp kullanılarak çözülebilen birçok başka sorun ortaya çıkar: 1. Farklı anlamları / değerleri kodlamak / saklamak için bir "kutu" kullanın ve gbu "kutulu değerleri" döndüren işlevlere sahip olun . 2. Çıktının girdisine g >>= fbağlanmasına yardımcı olmak için bestecilere / bağlayıcılara sahip olun , bu yüzden hiç değiştirmek zorunda değiliz .gff

  7. Bu teknik kullanılarak çözülebilen dikkat çekici sorunlar şunlardır:

    • fonksiyonlar dizisindeki her fonksiyonun ("program") paylaşabileceği küresel bir duruma sahip olmak: çözüm StateMonad.

    • "Saf olmayan fonksiyonları" sevmiyoruz: aynı giriş için farklı çıktılar veren fonksiyonlar . Bu nedenle, bu işlevleri işaretleyerek, etiketli / kutulu bir değer döndürmelerini sağlayalım: monad.IO

Toplam mutluluk !!!!


2
@DmitriZaitsev İstisnalar bildiğim kadarıyla sadece "saf olmayan kodda" (IO monad) meydana gelebilir.
cibercitizen1

3
@DmitriZaitsev Hiçbir şeyin rolü başka herhangi bir tür (beklenen Realden farklı) tarafından oynanamaz. Konu bu değil. Örnekte mesele, bir öncekinin beklenmedik bir değer türünü, ikincisini zincirlemeden (yalnızca bir Gerçeği giriş olarak kabul etmeksizin) diğerine döndürebildiği zaman bir zincirdeki işlevlerin nasıl uyarlanacağıdır.
cibercitizen1

3
Başka bir karışıklık noktası, "monad" kelimesinin cevabınızda sadece iki kez ve diğer terimlerle kombinasyon halinde görünmesidir - Stateve IObunların hiçbiri ve "monad" ın tam anlamı verilmez
Dmitri Zaitsev

31
Benim için OOP geçmişinden gelen bir kişi olarak bu cevap gerçekten bir monad sahibi olmanın arkasındaki motivasyonu ve aynı zamanda monad'ın gerçekte ne olduğunu açıkladı (kabul edilen bir cevaptan çok daha fazlası). Yani, çok yardımcı buluyorum. Çok teşekkürler @ cibercitizen1 ve +1
akhilless

3
Yaklaşık bir yıldır işlevsel programlama hakkında okuyordum. Bu cevap ve özellikle ilk iki nokta nihayet zorunlu programlamanın gerçekte ne anlama geldiğini ve fonksiyonel programlamanın neden farklı olduğunu anlamamı sağladı. Teşekkür ederim!
jrahhali

82

Monad'lara en yakın OO benzetmesinin " komut paterni " olduğunu söyleyebilirim .

Komut deseninde, bir komut nesnesine normal bir ifade veya ifade sararsınız. Komut nesnesi , wrapped deyimini yürüten bir execute yöntemini ortaya koyar. Böylece ifade, birinci sınıf nesnelere dönüştürülebilir ve iradesiyle çalıştırılabilir. Komutlar oluşturulabilir, böylece komut nesnelerini zincirleyerek ve iç içe geçirerek bir program nesnesi oluşturabilirsiniz.

Komutlar, ayrı bir nesne olan invoker tarafından yürütülür . Komut kalıbını kullanmanın yararı (sadece bir dizi sıradan ifade yürütmek yerine), farklı invokatörlerin komutların nasıl yürütüleceğine farklı mantık uygulayabilmeleridir.

Komut deseni, ana bilgisayar dili tarafından desteklenmeyen dil özellikleri eklemek (veya kaldırmak) için kullanılabilir. Örneğin, istisnasız varsayımsal bir OO dilinde, komutlara "try" ve "throw" yöntemlerini göstererek istisna semantiği ekleyebilirsiniz. Bir komut çağrıldığında, çağrı yapan son "dene" çağrısına kadar komut listesi (veya ağacı) boyunca geri gider. Bunun tersine, her bir komut tarafından atılan tüm istisnaları yakalayıp bir sonraki komuta geçirilen hata kodlarına dönüştürerek istisna anlamını bir dilden ( istisnaların kötü olduğuna inanıyorsanız ) kaldırabilirsiniz .

İşlemler, deterministik olmayan yürütme veya devam etme gibi daha süslü yürütme semantiği, yerel olarak desteklemeyen bir dilde bu şekilde uygulanabilir. Bunu düşünürseniz oldukça güçlü bir model.

Şimdi gerçekte komut kalıpları böyle genel bir dil özelliği olarak kullanılmamaktadır. Her bir ifadeyi ayrı bir sınıfa dönüştürmenin ek yükü, dayanılmaz miktarda kazan plakası koduna yol açacaktır. Ancak prensip olarak, fp'de monadlar ile aynı problemleri çözmek için kullanılabilir.


15
Bunun işlevsel programlama kavramlarına dayanmayan ve gerçek OOP terimlerine koyan ilk gördüğüm monad açıklaması olduğuna inanıyorum. Gerçekten iyi bir cevap.
David K. Hess

bu, FP / Haskell'de monad'ların gerçekte ne olduğuna çok yakındır, ancak komut nesnelerinin kendilerinin hangi "çağırma mantığına" ait olduklarını "bildiklerini" (ve yalnızca uyumlu olanları birlikte zincirleyebildiklerini); invoker sadece ilk değeri sağlar. "Yazdır" komutunun "deterministik olmayan yürütme mantığı" tarafından çalıştırılabileceği gibi değil. Hayır, "I / O mantığı" (yani IO monad) olmalıdır. Ama bunun dışında çok yakın. Hatta Monad'ların sadece Programlar olduğunu söyleyebilirsin (Daha sonra yürütülecek olan Kod İfadelerinden oluşur). İlk günlerde "bağlama", "programlanabilir noktalı virgül" olarak bahsedildi .
Ness Ness

1
Aslında, temel FP kavramlarını açıklamak için FP kullanan cevaplara, özellikle de Scala gibi bir FP dili kullanan cevaplara gerçekten şüpheyle yaklaşıyorum. Aferin JacquesB!
Monica

62

Bir OOP programcısının (herhangi bir işlevsel programlama arka planı olmadan) anlayacağı anlamıyla, monad nedir?

Hangi sorunu çözüyor ve en yaygın kullanıldığı yerler hangileri?

OO programlama açısından, bir monad, iki yöntemle bir türle parametrelendirilen returnve aşağıdakileri bindtanımlayan bir arabirimdir (veya büyük olasılıkla bir karışımdır) :

  • Enjekte edilen değer türünün monadik bir değerini elde etmek için bir değer nasıl enjekte edilir;
  • Monadik olmayan bir değerden monadik bir değer üreten, monadik bir değerde bir işlev nasıl kullanılır.

Çözdüğü sorun, herhangi bir arabirimden bekleyebileceğiniz aynı türden bir sorun, yani "Farklı şeyler yapan bir sürü farklı sınıfım var, ancak bu farklı şeyleri temeldeki benzerliklere sahip bir şekilde yapıyor gibi görünüyor. sınıfların kendileri 'Nesne' sınıfının kendisinden daha yakın bir şeyin alt türleri olmasalar bile, aralarındaki benzerliği tarif edebilir miyim? "

Daha spesifik olarak Monad"arayüz", kendisinin bir tür aldığı bir türe benzer IEnumeratorveya IIteratorbu tarzdadır. MonadGerçi ana "nokta" , ana sınıfın bilgi yapısını korurken, hatta geliştirirken yeni bir "iç tip" olan noktaya bile iç tipi temel alan işlemleri bağlayabilmektir.


1
returngerçekte monad üzerinde bir yöntem olmaz çünkü bir monad örneğini argüman olarak almaz. (yani: bu / ben yok)
Laurence Gonsalves

@LaurenceGonsalves: Şu anda bunu lisans tezim için aradığımdan, çoğunlukla sınırlayıcı olanın C # / Java arayüzlerinde statik yöntemlerin olmaması olduğunu düşünüyorum. Tipik sınıflara dayanmak yerine en azından statik olarak bağlı olan tüm monad hikayesini uygulama yönünde çok yol kat edebilirsiniz. İlginçtir, daha yüksek türlerin eksikliğine rağmen bu bile işe yarayacaktır.
Sebastian Graf

42

"Bir son sunumumuz var - tipi anksiyete üzerine profesyonel yardım Monadologie tarafından" Christopher Ligi devamı ve monadın konularda oldukça ilginç (12 Temmuz 2010).
Bu (slayt paylaşımı) sunumuyla giden video aslında vimeo'da mevcut .
Monad bölümü bu bir saatlik videoda yaklaşık 37 dakika içinde başlar ve 58 slayt sunumunun 42. slaytıyla başlar.

"Fonksiyonel programlama için önde gelen tasarım deseni" olarak sunulur, ancak örneklerde kullanılan dil hem OOP hem de fonksiyonel olan Scala'dır.
Sen Blog yazısı içinde Scala Monad daha fazla bilgi bulabilirsiniz " monads - Scala soyut hesaplamaları diğer bir yolu " ndan, Debasish Ghosh (27 Mart 2008).

M tipi bir yapıcı şu işlemleri destekliyorsa bir monaddır:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

Örneğin (Scala'da):

  • Option bir monad
    def ünitesi [A] (x: A): Seçenek [A] = Bazı (x)

    def flatMap [A, B] (m: Seçenek [A]) (f: A => Seçenek [B]): Seçenek [B] =
      eşleşme {
       case Yok => Yok
       case Bazı (x) => f (x)
      }
  • List Monad mı
    def birimi [A] (x: A): Liste [A] = Liste (x)

    def flatMap [A, B] (m: Liste [A]) (f: A => Liste [B]): Liste [B] =
      eşleşme {
        vaka Nil => Nil
        case x :: xs => f (x) ::: flatMap (xs) (f)
      }

Monad, Monad yapılarından yararlanmak için oluşturulmuş uygun sözdizimi nedeniyle Scala'da önemli bir yer tutuyor:

forScala'da anlama :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

derleyici tarafından şu dile çevrilir:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

Anahtar soyutlama, flatMaphesaplamayı zincirleme bağlar.
Her çağırma, flatMapzincirdeki bir sonraki komutun girişi olarak işlev gören aynı veri yapısı türünü (ancak farklı değerde) döndürür.

Yukarıdaki snippet'te flatMap girdi olarak bir kapanış alır ve a değerini (SomeType) => List[AnotherType]döndürür List[AnotherType]. Dikkat edilmesi gereken önemli nokta, tüm flatMaps'in girişle aynı kapatma türünü alması ve çıktıyla aynı türü döndürmesidir.

Hesaplama iş parçacığını "bağlayan" işte budur - kavrayıştaki dizinin her öğesi aynı tür kısıtlamaya uymak zorundadır.


İki işlem yaparsanız (başarısız olabilir) ve sonucu üçüncüye iletirseniz, örneğin:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

ancak Monad'dan yararlanmadan, kıvrımlı OOP kodu elde edersiniz:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

Monad ile, tüm işlemler gibi gerçek türlerle ( Venue, User) çalışabilir ve Seçenek sözdizimini, sözdiziminin işlevlerinden dolayı gizli tutabilirsiniz:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

Verim kısmı, yalnızca üç fonksiyonun tümü varsa yürütülür Some[X]; herhangi bir Nonedoğrudan iade edilecektir confirm.


Yani:

Monadlar, Fonksiyonel Programlama içinde sıralı hesaplamaya izin verir, bu da eylemlerin sıralanmasını bir DSL gibi biraz iyi yapılandırılmış bir biçimde modellememizi sağlar.

Ve en büyük güç, farklı amaçlara hizmet eden monadları, bir uygulama içindeki genişletilebilir soyutlamalara dönüştürme yeteneğiyle gelir.

Bir monad tarafından eylemlerin bu sıralanması ve işlenmesi, kapanışların büyüsü yoluyla dönüşümü yapan dil derleyicisi tarafından yapılır.


Bu arada, Monad sadece FP'de kullanılan bir hesaplama modeli değildir:

Kategori teorisi birçok hesaplama modeli önerir. Onların arasında

  • Okların hesaplama modeli
  • Monad hesaplama modeli
  • Uygulamalı hesaplama modeli

2
Bu açıklamayı seviyorum! Verdiğiniz örnek, konsepti güzel bir şekilde ortaya koyuyor ve IMHO'nun Eric'in SelectMany () 'nin bir Monad olma konusundaki teaser'ından eksik olduğunu da ekliyor. Bunun için teşekkürler!
aven

1
IMHO bu en şık cevap
Polymerase

ve her şeyden önce Functor.
Ness

34

Hızlı okuyuculara saygı duymak için önce kesin tanımla başlarım, daha hızlı "düz İngilizce" açıklamasıyla devam edip örneklere geçiyorum.

İşte biraz kısa ve öz bir tanım :

Bir monad (bilgisayar biliminde) resmen şu şekildedir:

  • Xbelirli bir programlama dilinin her türünü yeni bir türe gönderir T(X)("içindeki Tdeğerler içeren hesaplamaların türü" olarak adlandırılır X);

  • formun iki işlevini f:X->T(Y)ve g:Y->T(Z)bir işlevi oluşturmak için bir kural ile donatılmış g∘f:X->T(Z);

  • belli bir birim işlev ile ilgili olarak açık bir şekilde birleştirici ve çağrışımcı olmayan bir şekilde, pure_X:X->T(X)bu değeri döndüren saf hesaplamaya bir değer kattığı düşünülmelidir.

Yani basit bir deyişle, bir monad bir olan her türünden geçmesine kural Xbaşka bir türeT(X) ve bir kuralı iki işlevlerden geçmesine f:X->T(Y)ve g:Y->T(Z)yeni işleve (eğer oluşturmak istiyorum ama yapamıyor)h:X->T(Z) . Bununla birlikte, katı matematiksel anlamda kompozisyon değildir . Temelde fonksiyonun kompozisyonunu "büküyoruz" ya da fonksiyonların nasıl oluştuğunu yeniden tanımlıyoruz.

Ayrıca, monad'ın "bariz" matematiksel aksiyomları karşılamak için besteleme kuralına ihtiyacımız var:

  • Birleşim : Oluşturma file gve daha sonra h(dışarıdan) oluşturma ile aynı olması gerekir gile hve daha sonra f(içeriden).
  • Unital özellik : Her iki tarafta kimlik fonksiyonu file bestelemek verim vermelidir .f

Yine, basit bir deyişle, fonksiyon kompozisyonumuzu istediğimiz gibi yeniden tanımlayarak deliremeyiz:

  • Öncelikle ilişkilendirmeye, örneğin üst üste birkaç işlev oluşturabilmek f(g(h(k(x)))ve sipariş oluşturma işlev çiftlerini belirtme konusunda endişelenmemek gerekir. Monad kuralı, sadece bir çift ​​fonksiyonun nasıl oluşturulacağını öngördüğü için , bu aksiyom olmadan, ilk önce hangi çiftin oluştuğunu bilmemiz gerekir. (Bu Yerdeğiştirme özelliğinden farklı olan Not file oluşan golarak aynıydı gile oluşan fhalen uygulanmakta olduğu gibi).
  • İkincisi, unital mülkiyete ihtiyacımız var, yani kimliklerin önemsiz bir şekilde onları beklediğimiz şekilde oluşturduğunu söylemek. Böylece, bu kimlikler çıkartıldığında işlevleri güvenle yeniden düzenleyebiliriz.

Kısaca tekrar: Bir monad, iki aksiyomu - ilişkilendirilebilirlik ve unital özellik - tatmin eden tür genişletme ve oluşturma işlevleri kuralıdır.

Pratik anlamda, monad'ın sizin için fonksiyon oluşturmaya özen gösterecek dil, derleyici veya çerçeve ile sizin için uygulanmasını istersiniz. Böylece, uygulamalarının nasıl uygulandığından endişelenmek yerine, işlevinizin mantığını yazmaya odaklanabilirsiniz.

Kısacası budur.


Profesyonel matematikçi olmak, çağıran kaçınmayı tercih h"tertibi" fve g. Çünkü matematiksel olarak değil. "Kompozisyon" olarak adlandırmak, yanlış bir hşekilde gerçek matematiksel kompozisyon olduğunu varsayar . Hatta benzersiz tarafından belirlenmez fve g. Bunun yerine, monadımızın yeni "besteleme kuralı" fonksiyonlarının bir sonucudur. İkincisi olsa bile gerçek matematiksel kompozisyondan tamamen farklı olabilir!


Daha az kuru yapmak için, küçük bölümlerle açıklama eklediğimden örnek göstermeye çalışalım, böylece doğrudan noktaya atlayabilirsiniz.

Monad örnekleri olarak istisna atma

İki işlev oluşturmak istediğimizi varsayalım:

f: x -> 1 / x
g: y -> 2 * y

Ancak f(0)tanımlanmamıştır, bu nedenle bir istisna eatılır. O zaman kompozisyon değerini nasıl tanımlayabilirsiniz g(f(0))? Tabii ki yine bir istisna atın! Belki de aynı e. Belki yeni bir güncellenmiş istisna e1.

Burada tam olarak ne oluyor? İlk olarak, yeni istisna değerlerine (farklı veya aynı) ihtiyacımız var. Onları nothingya nullda herhangi bir şeyi çağırabilirsiniz, ancak öz aynı kalır - bunlar yeni değerler olmalı number, örneğin buradaki örneğimizde bir olmamalıdır . Herhangi bir dilde nullnasıl nulluygulanabileceği konusunda kafa karışıklığını önlemek için onları aramamayı tercih ederim . Aynı şekilde kaçınmayı tercih ederim, nothingçünkü nullprensip olarak ne nullyapılması gerektiği ile ilişkilidir , ancak bu prensip pratik nedenlerden ötürü sıklıkla bükülür.

İstisna tam olarak nedir?

Bu, deneyimli bir programcı için önemsiz bir konudur, ancak sadece herhangi bir karışıklık solucanını söndürmek için birkaç kelime bırakmak istiyorum:

İstisna, geçersiz yürütme sonucunun nasıl gerçekleştiği hakkında bilgi içeren bir nesnedir.

Bu, herhangi bir ayrıntıyı atmaktan ve tek bir küresel değer döndürmek ( NaNveya gibi null) veya uzun bir günlük listesi oluşturmak veya tam olarak ne olduğu, bir veritabanına göndermek ve dağıtılmış veri depolama katmanının her yerinde çoğaltmak;)

Bu iki istisna örneği arasındaki önemli fark, ilk durumda hiçbir yan etkinin olmamasıdır . İkincisinde var. Bu da bizi (bin dolar) sorusuna getiriyor:

Saf işlevlerde istisnalara izin veriliyor mu?

Daha kısa cevap : Evet, ancak sadece yan etkilere yol açmadıkları zaman.

Daha uzun cevap. Saf olmak için, işlevinizin çıktısı girdisi tarafından benzersiz bir şekilde belirlenmelidir. Bu nedenle , istisna dediğimiz yeni soyut değere fgöndererek işlevimizi değiştiriyoruz . Değerin , girdimiz tarafından benzersiz bir şekilde belirlenmeyen hiçbir dış bilgi içermediğinden emin oluruz . İşte yan etkisi olmayan bir istisna örneği:0eex

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

Ve burada yan etkisi olan bir tane var:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

Aslında, sadece bu mesaj gelecekte değişebiliyorsa yan etkileri vardır. Ancak asla değişmeyeceği garanti edilirse, bu değer benzersiz bir şekilde öngörülebilir hale gelir ve bu nedenle yan etki yoktur.

Daha da keskin yapmak için. 42Hiç geri dönen bir fonksiyon açıkça saftır. Ama eğer birileri 42bu değerin değişebileceği bir değişken yapmaya karar verirse , aynı işlev yeni koşullar altında saf olmayı bırakır.

Özü göstermek için basitlik için nesne değişmez gösterim kullandığımı unutmayın. Ne yazık ki işler, errorfonksiyon kompozisyonu açısından burada istediğimiz gibi davranan bir tür olmayan JavaScript gibi dillerde dağılmışken, gerçek türler bu şekilde davranıyor nullveya NaNdavranmıyor, aksine bazı yapay ve her zaman sezgisel değil dönüşümleri yazın.

Tip uzantısı

İstisnamızın içindeki mesajı değiştirmek istediğimizden, Eistisna nesnesinin tamamı için gerçekten yeni bir tip ilan ediyoruz ve o zaman maybe number, türden numberveya yeni istisna tipinden olan kafa karıştırıcı adının yanı sıra, bu ne yapar Ebu yüzden gerçekten birliktir number | Earasında numberve E. Özellikle, Ene önerilen ne de isme yansıyan , nasıl inşa etmek istediğimize bağlıdır maybe number.

Fonksiyonel kompozisyon nedir?

Fonksiyonları alan f: X -> Yve g: Y -> Zkompozisyonlarını fonksiyon h: X -> Zdoyurucu olarak inşa eden matematiksel işlemdir h(x) = g(f(x)). Bu tanımdaki sorun, sonucun f(x)argümanı olarak kullanılmasına izin verilmediğinde ortaya çıkar g.

Matematikte bu işlevler fazladan çalışma olmadan oluşturulamaz. Yukarıdaki örneğimiz için kesinlikle matematiksel çözüm fve tanım kümesinden gkaldırmaktır . Bu yeni tanım kümesiyle (daha kısıtlayıcı yeni tür ), birleştirilebilir hale gelir .0fxfg

Bununla birlikte, programlamada fböyle bir tanım kümesini kısıtlamak çok pratik değildir . Bunun yerine istisnalar kullanılabilir.

Ya da başka bir yaklaşım olarak, yapay değerler gibi oluşturulur NaN, undefined, null, Infinitydeğerlendirip Yani vb 1/0için Infinityve 1/-0için -Infinity. Ve sonra yeni değeri istisna atmak yerine ifadenize zorlayın. Tahmin edilebilir bulabileceğiniz veya bulamayacağınız sonuçlara yol açar:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

Ve devam etmeye hazır normal sayılara geri döndük;)

JavaScript, yukarıdaki örnekte olduğu gibi hata atmadan herhangi bir maliyetle sayısal ifadeleri yürütmemizi sağlar. Bu, işlevlerin oluşturulmasına da izin verdiği anlamına gelir. Monad tam olarak bununla ilgilidir - bu cevabın başında tanımlandığı gibi aksiyomları tatmin eden fonksiyonlar oluşturmak bir kuraldır.

Ancak, JavaScript'in sayısal hatalarla başa çıkma uygulamasından doğan işlev oluşturma kuralı bir monad mı?

Bu soruyu cevaplamak için ihtiyacınız olan tek şey aksiyomları kontrol etmektir (burada sorunun bir parçası olarak egzersiz olarak bırakılır;).

Fırlatma istisnası bir monad inşa etmek için kullanılabilir mi?

Gerçekten de, daha yararlı bir monad bunun yerine f, bazıları için istisna atarsa x, kompozisyonunun herhangi biriyle olduğu gibi reçete eden kural olacaktır g. Ayrıca, istisnayı Eşimdiye kadar sadece bir olası değerle ( kategori teorisindeki terminal nesnesi ) global olarak benzersiz kılın . Şimdi iki aksiyom anında kontrol edilebilir ve çok kullanışlı bir monad elde ederiz. Ve sonuç, belki de monad olarak bilinen şeydir .


3
İyi katkı. +1 Ama belki de "açıklamaların çoğunu çok uzun bulmuşlar ..." ı silmek istiyor olabilirsiniz. Diğerleri ise "düz ingilizce" olup olmadığını şu şekilde değerlendirecektir: "düz ingilizce == basit bir şekilde, basit bir şekilde".
cibercitizen1

@ cibercitizen1 Teşekkürler! Örneği saymazsanız aslında kısadır. Ana nokta, tanımı anlamak için örneği okumanıza gerek olmamasıdır . Ne yazık ki birçok açıklama beni önce örnekleri okumak zorunda bırakıyor , bu genellikle gereksizdir, ancak elbette yazar için ekstra çalışma gerektirebilir. Belirli örneklere çok fazla güvenerek, önemsiz ayrıntıların resmi gizlemesi ve kavramasını zorlaştırması tehlikesi vardır. Bunu söyledikten sonra, geçerli puanlarınız var, güncellemeye bakın.
Dmitri Zaitsev

2
çok uzun ve kafa karıştırıcı
seenimurugan

1
@seenimurugan İyileştirme önerileri bekliyoruz;)
Dmitri Zaitsev

26

Monad, bir değeri kapsayan ve esasen iki işlemin uygulanabildiği bir veri türüdür:

  • return x kapsayan bir monad türünün değerini oluşturur x
  • m >>= f("bağlama operatörü" olarak okuyun) işlevi fmanaddaki değere uygularm

Bir monad budur. Orada birkaç teknik bir , ama temelde bu iki operasyon bir monad tanımlar. Asıl soru şudur: "Bir monad ne yapar ?" Ve bu monad listelerine bağlıdır; monad, Maybes monad, IO işlemleri monad. Biz bu şeyler monads vardır derken bunun anlamı tüm onlar monad arayüze sahip olmasıdır returnve >>=.


“Bir monad ne yapar ve bu monad'a bağlıdır”: ve daha doğrusu, bindher monadik tip için tanımlanması gereken fonksiyona bağlıdır , değil mi? Bağlamanın kompozisyonla karıştırılmaması için iyi bir neden olacaktır, çünkü kompozisyon için tek bir tanım vardır, ancak bir bağlanma fonksiyonu için sadece tek bir tanım olamaz, doğru anlarsam monadik tip başına bir tane vardır.
Hibou57

14

Gönderen wikipedia :

Fonksiyonel programlamada, bir monad, hesaplamaları (alan modelindeki veriler yerine) temsil etmek için kullanılan bir tür soyut veri türüdür. Monadlar, programcının eylemleri, her eylemin monad tarafından sağlanan ek işleme kuralları ile süslendiği bir boru hattı oluşturmak için birlikte zincirlemesine izin verir. Fonksiyonel tarzda yazılmış programlar, sıralı işlemleri, 1 [2] içeren prosedürleri yapılandırmak veya keyfi kontrol akışlarını tanımlamak için (eşzamanlılık, süreklilikler veya istisnalar gibi) monad'lardan yararlanabilir.

Resmi olarak, bir monad, monadik fonksiyonların (yani monaddaki değerleri argüman olarak kullanan fonksiyonlar) doğru kompozisyonuna izin vermek için çeşitli özellikleri yerine getirmesi gereken iki işlem (bağlama ve geri dönüş) ve bir tip yapıcı M tanımlanarak oluşturulur. İade işlemi düz tipten bir değer alır ve onu M tipi monadik bir kaba koyar. Bağlama işlemi, ters işlemi gerçekleştirir, orijinal değeri konteynırdan çıkarır ve boru hattındaki ilgili sonraki işleve geçirir.

Bir programcı, bir veri işleme boru hattını tanımlamak için monadik fonksiyonlar oluşturacaktır. Monad, boru hattındaki belirli monadik işlevlerin çağrılma sırasına karar veren ve hesaplamanın gerektirdiği tüm gizli işleri yöneten yeniden kullanılabilir bir davranış olduğu için bir çerçeve görevi görür. [3] Boru hattında serpiştirilen bağlama ve geri dönüş operatörleri, her monadik fonksiyon kontrolü geri döndürdükten sonra yürütülür ve monad tarafından ele alınan belirli hususlarla ilgilenir.

Çok iyi açıkladığına inanıyorum.


12

OOP terimlerini kullanarak yönetebileceğim en kısa tanımı yapmaya çalışacağım:

Genel bir sınıf CMonadic<T>, en azından aşağıdaki yöntemleri tanımladığında bir monaddır:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

ve tüm T türleri ve olası değerleri t için aşağıdaki yasalar geçerliyse

sol kimlik:

CMonadic<T>.create(t).flatMap(f) == f(t)

doğru kimlik

instance.flatMap(CMonadic<T>.create) == instance

birleşebilirlik:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Örnekler :

Liste monad'ında şunlar olabilir:

List<int>.create(1) --> [1]

Ve listedeki flatMap [1,2,3] şu şekilde çalışabilir:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Yinelenebilirler ve Gözlemlenebilirler de Vaatler ve Görevler gibi monadik yapılabilir.

Yorum :

Monadlar o kadar karmaşık değil. flatMapFonksiyon çok daha sık karşılaşılan gibidir map. Genel sınıftan gelen bir değerle çağırabileceği (hemen veya daha sonra, sıfır veya daha fazla kez) bir işlev bağımsız değişkeni (temsilci olarak da bilinir) alır. Geçilen işlevin, dönüş değerini aynı tür genel sınıfta da sarmasını bekler. Buna yardımcı olmak için, createbir değerden o genel sınıfın bir örneğini oluşturabilen bir kurucu sağlar . FlatMap'in döndürme sonucu, aynı tipte genel bir sınıftır ve genellikle flatMap'in bir veya daha fazla uygulamasının önceki sonuçlara döndürülen sonuçlarında bulunan aynı değerleri içerir. Bu, flatMap'i istediğiniz kadar zincirlemenize olanak tanır:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

Öyle ki, bu tür bir jenerik sınıf çok sayıda şey için temel model olarak yararlıdır. Bu (kategori teorisi jargonizmleri ile birlikte) Monad'ların anlamak veya açıklamak için çok zor görünmesinin nedenidir. Çok soyut bir şeydir ve sadece uzmanlaştıklarında açıkça faydalı olurlar.

Örneğin, monadik kapları kullanarak istisnaları modelleyebilirsiniz. Her kap, işlemin sonucunu veya oluşan hatayı içerecektir. FlatMap geri çağrıları zincirindeki bir sonraki işlev (delege), yalnızca bir öncekinin kapta bir değer paketlemesi durumunda çağrılır. Aksi takdirde, bir hata paketlenmişse, hata adı verilen bir yöntemle bağlı bir hata işleyici işlevine sahip bir kap bulunana kadar zincirleme kapları boyunca yayılmaya devam eder .orElse()(böyle bir yönteme izin verilen bir uzantı olur)

Notlar : İşlevsel diller, her tür monadik genel sınıfta çalışabilen işlevler yazmanıza olanak tanır. Bunun çalışması için, monadlar için genel bir arayüz yazılması gerekir. C # böyle bir arayüz yazmak mümkün olup olmadığını bilmiyorum, ama bildiğim kadarıyla değil:

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

7

Bir monad'ın OO'da "doğal" bir yorumu olup olmadığı monad'a bağlıdır. Java gibi bir dilde, belki monad'ı boş işaretçileri kontrol etme diline çevirebilirsiniz, böylece başarısız olan hesaplamalar (yani, Haskell'de Hiçbir Şey üretmez) sonuç olarak boş işaretçiler yayar. Durum monadını, değiştirilebilir bir değişken ve durumunu değiştirme yöntemleri oluşturarak üretilen dile çevirebilirsiniz.

Bir monad, endofunktör kategorisinde bir monoiddir.

Cümlenin bir araya getirdiği bilgi çok derindir. Ve herhangi bir zorunlu dilde bir monadda çalışıyorsunuz. Bir monad, "sıralı" alana özgü bir dildir. Birlikte bir monad'ı “zorunlu programlama” nın matematiksel bir modeli yapan bazı ilginç özellikleri karşılar. Haskell, çeşitli şekillerde birleştirilebilen küçük (veya büyük) zorunlu dilleri tanımlamayı kolaylaştırır.

Bir OO programcısı olarak, bir bağlamda çağrılabilecek işlev veya yordam türlerini, bir nesne olarak adlandırdığınız şeyi düzenlemek için dilinizin sınıf hiyerarşisini kullanırsınız. Bir monad, farklı monadların keyfi yollarla birleştirilebildiği sürece, alt-monad'ın tüm yöntemlerini etkin bir şekilde "içe aktarabildiği" sürece bu fikir üzerinde bir soyutlamadır.

Mimari olarak, bir değer hesaplamak için hangi bağlamların kullanılabileceğini açıkça ifade etmek için tür imzaları kullanılır.

Bu amaçla monad transformatörleri kullanılabilir ve tüm "standart" monadların yüksek kalitede bir koleksiyonu vardır:

  • Listeler (bir listeyi etki alanı olarak ele alarak deterministik olmayan hesaplamalar)
  • Belki (başarısız olabilen, ancak raporlamanın önemsiz olduğu hesaplamalar)
  • Hata (başarısız olabilen ve özel durum işleme gerektiren hesaplamalar
  • Okuyucu (düz Haskell fonksiyonlarının kompozisyonlarıyla temsil edilebilen hesaplamalar)
  • Yazar (sıralı "oluşturma" / "günlüğe kaydetme" (dize, html vb.)
  • Devam (devam)
  • IO (temeldeki bilgisayar sistemine bağlı hesaplamalar)
  • Durum (bağlamı değiştirilebilir bir değer içeren hesaplamalar)

ilgili monad transformatörleri ve tip sınıfları ile. Tip sınıfları, monadları arayüzlerini birleştirerek birleştirmeye tamamlayıcı bir yaklaşım sağlar, böylece beton monadlar monad "tür" için standart bir arayüz uygulayabilir. Örneğin, Control.Monad.State modülü bir MonadState sm sınıfı içerir ve (State s) formun bir örneğidir

instance MonadState s (State s) where
    put = ...
    get = ...

Uzun hikaye, bir monad'ın, değere "bağlam" ekleyen, monad'a bir değer enjekte etmenin bir yolu olan ve en azından ona eklenmiş bağlamla ilgili değerleri değerlendirmenin bir yolu olan bir işlevci olmasıdır. sınırlı bir şekilde.

Yani:

return :: a -> m a

a tipi bir değeri m a tipi bir monad "eylemine" enjekte eden bir işlevdir.

(>>=) :: m a -> (a -> m b) -> m b

monad eylemi gerçekleştiren, sonucunu değerlendiren ve sonuca bir fonksiyon uygulayan bir fonksiyondur. (>> =) ile ilgili en güzel şey, sonucun aynı monad'da olmasıdır. Başka bir deyişle, m >> = f'de, (>> =) sonucu m'den çıkarır ve f'ye bağlar, böylece sonuç monad'da olur. (Alternatif olarak, (>> =) 'nin f'yi m içine çektiğini ve sonuca uyguladığını söyleyebiliriz.) Sonuç olarak, eğer f :: a -> mb ve g :: b -> mc varsa, "dizi" eylemleri:

m >>= f >>= g

Veya "notasyon" kullanarak

do x <- m
   y <- f x
   g y

(>>) türü aydınlatıcı olabilir. Bu

(>>) :: m a -> m b -> m b

C gibi prosedür dillerinde (;) operatörüne karşılık gelir.

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

Matematiksel ve felsefi mantıkta, monadizmle "doğal olarak" modellenen çerçeveler ve modellerimiz var. Yorum, modelin alanına bakan ve bir teklifin (veya genellemeler altında formülün) gerçek değerini (veya genellemelerini) hesaplayan bir fonksiyondur. Zorunlu bir mantık mantığında, "olası her dünyada" doğruysa bir teklifin gerekli olduğunu söyleyebiliriz - eğer kabul edilebilir her alan için doğruysa. Bu, bir teklif için bir dilde bir modelin, etki alanı farklı modeller (olası her dünyaya karşılık gelen) koleksiyonundan oluşan bir model olarak kabul edilebileceği anlamına gelir. Her monadın katmanları düzleştiren "join" adlı bir yöntemi vardır, bu da sonucu monad eylemi olan her monad eyleminin monad içine gömülebileceğini gösterir.

join :: m (m a) -> m a

Daha da önemlisi, monadın "katman istifleme" işlemi altında kapalı olduğu anlamına gelir. Monad transformatörleri şu şekilde çalışır: monadları, aşağıdaki türler için "birleştirme benzeri" yöntemler sağlayarak birleştirir

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

böylece (MaybeT m) 'deki bir eylemi m cinsinden bir eyleme dönüştürebiliriz. Bu durumda, runMaybeT :: MaybeT ma -> m (Belki a) birleştirmeye benzer yöntemimizdir. (Belki m) bir monad, belki de :: m (Belki a) -> Belki de ma, m cinsinden yeni bir monad eylemi için etkili bir kurucudur.

Bir functor için serbest bir monad, f'nin istiflenmesi ile üretilen monaddır; bununla birlikte, f için her yapıcı dizisi, serbest monadın bir elemanı (veya daha doğrusu, f). Serbest monadlar, minimum miktarda kazan plakalı esnek monadlar oluşturmak için yararlı bir tekniktir. Bir Haskell programında, tip güvenliğinin korunmasına yardımcı olmak için "yüksek seviye sistem programlama" için basit monadları tanımlamak için ücretsiz monadları kullanabilirim (sadece türleri ve bildirimlerini kullanıyorum. Birleştiricilerin kullanımı ile uygulamalar basittir):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Monadizm, "yorumlayıcı" veya "komut" deseni olarak adlandırabileceğiniz, en net biçimine soyutlanan temel mimaridir, çünkü her monadik hesaplamanın en azından önemsiz bir şekilde "çalıştırılması" gerekir. (Çalışma zamanı sistemi bizim için IO monadını çalıştırır ve herhangi bir Haskell programının giriş noktasıdır. IO, IO eylemlerini sırayla çalıştırarak diğer hesaplamaları "yönlendirir".

Birleştirme türü, bir monadın endofunktör kategorisinde bir monoid olduğu ifadesini aldığımız yerdir. Birleşim türü nedeniyle teorik amaçlar için genellikle daha önemlidir. Ancak türü anlamak, monadları anlamak anlamına gelir. Birleşim ve monad transformatörün birleşimine benzer tipleri, işlev bileşimi anlamında etkili bir şekilde endofunktör bileşimleridir. Haskell benzeri bir sahte dile koymak için,

Foo :: m (ma) - (m. M) a


3

Monad bir işlevler dizisidir

(Pst: bir fonksiyon dizisi sadece bir hesaplamadır).

Aslında, gerçek bir dizi (bir hücre dizisindeki bir fonksiyon) yerine, bu fonksiyonların başka bir fonksiyon tarafından zincirlenmesi >> =. >> =, i işlevinden gelen sonuçları i + 1 besleme işlevine uyarlamaya, aralarında hesaplamalar yapmaya, hatta i + 1 işlevini çağırmamaya izin verir.

Burada kullanılan türler "bağlamlı türler" dir. Bu, "etiketi" olan bir değerdir. Zincirleme fonksiyonlar "açık değer" almalı ve etiketli bir sonuç döndürmelidir. >> = 'nin görevlerinden biri, bağlamından çıplak bir değer elde etmektir. Ayrıca çıplak bir değer alan ve bir etiketle koyan "return" işlevi de vardır.

Belki bir örnek . Bunu hesaplamalar yapan basit bir tamsayıyı saklamak için kullanalım.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

Monad'ların yardımcı işlemlere sahip işlevler dizisi olduğunu göstermek için, yukarıdaki örneğe eşdeğer, sadece gerçek bir işlev dizisi kullanarak düşünün

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

Ve şu şekilde kullanılır:

print (runMyMonad (Just 160) myArray1)

1
Süper temiz! Bağlama, bağlamı olan bir girdide sırayla, bağlamı olan bir dizi işlevi değerlendirmenin bir yoludur :)
Musa Al-hassy

>>=bir operatör
user2418306

1
Bence "fonksiyon dizisi" benzetmesi pek açıklığa kavuşmuyor. Eğer \x -> x >>= k >>= l >>= mbir fonksiyon dizisi ise h . g . f, monadları içermeyen de öyle.
16'da

biz söyleyebiliriz fanktorlar , monadic uygulamalı veya düz olsun, üzeresiniz "süslenmiş uygulaması" . 'uygulanabilir' zincirleme ekler ve 'monad' bağımlılık ekler (yani bir önceki hesaplama adımından elde edilen sonuçlara bağlı olarak bir sonraki hesaplama adımını oluşturmak).
Ness Ness

3

OO terimleriyle, bir monad akıcı bir kaptır.

Minimum gereksinim, bir yapıcıyı ve en az bir yöntemi class <A> Somethingdestekleyen bir tanımdır.Something(A a)Something<B> flatMap(Function<A, Something<B>>)

Muhtemelen, monad sınıfınızda Something<B> work()sınıfın kurallarını koruyan imzalı herhangi bir yöntem olup olmadığını da sayar - derleyici flatMap'te derleme zamanında pişer.

Bir monad neden yararlıdır? Çünkü anlambilimi koruyan zincirleme işlemlere izin veren bir kaptır. Örneğin, Optional<?>için isPresent semantik korur Optional<String>, Optional<Integer>, Optional<MyClass>vs.

Zor bir örnek olarak,

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

Bir dize ile başlayıp bir tamsayı ile sona erdiğimizi unutmayın. Oldukça havalı.

OO'da, biraz el sallama gerekebilir, ancak Something'in başka bir şeyden başka bir alt sınıfını döndüren herhangi bir yöntem, orijinal türde bir kap döndüren bir kap işlevi işlevinin ölçütlerini karşılar.

Anlambilimi bu şekilde korursunuz - yani kabın anlamı ve işlemleri değişmez, sadece kabın içindeki nesneyi sarar ve geliştirir.


2

Tipik kullanımdaki monadlar, prosedürel programlamanın istisna işleme mekanizmalarının işlevsel eşdeğeridir.

Modern yordamsal dillerde, bir ifade dizisi etrafına bir istisna işleyici koyarsınız, bunlardan herhangi biri bir istisna oluşturabilir. İfadelerden herhangi biri bir istisna atarsa, ifade dizisinin normal yürütülmesi durur ve bir istisna işleyicisine aktarılır.

Bununla birlikte, işlevsel programlama dilleri, felsefi olarak, "goto" gibi doğası gereği istisna işleme özelliklerinden kaçınırlar. Fonksiyonel programlama perspektifi, fonksiyonların program akışını kesintiye uğratan istisnalar gibi "yan etkilere" sahip olmaması gerektiğidir.

Gerçekte, yan etkiler esas olarak G / Ç nedeniyle gerçek dünyada göz ardı edilemez. Fonksiyonel programlamadaki Monad'lar, bir dizi zincirleme fonksiyon çağrısı alarak (bunlardan herhangi biri beklenmedik bir sonuç üretebilir) ve beklenmedik herhangi bir sonucu, kalan fonksiyon çağrıları boyunca güvenli bir şekilde akabilecek kapsüllenmiş verilere dönüştürerek bunu ele almak için kullanılır.

Kontrol akışı korunur, ancak beklenmedik olay güvenli bir şekilde kapsüllenir ve işlenir.


2

Bir Marvel'in örnek çalışma ile basit monad'ların açıklama burada .

Monadlar, etkili olan bağımlı fonksiyonları sıralamak için kullanılan soyutlamalardır. Burada etkili olan, F [A] biçiminde bir tür döndürdüğü anlamına gelir; örneğin Seçenek [A], burada Seçenek F'dir, tür oluşturucu olarak adlandırılır. Bunu 2 basit adımda görelim

  1. Aşağıda Fonksiyon bileşimi geçişlidir. A'dan CI'ye gitmek A => B ve B => C oluşturabilir.
 A => C   =   A => B  andThen  B => C

resim açıklamasını buraya girin

  1. Ancak, işlev Seçenek [A] yani A => F [B] gibi bir efekt türü döndürürse, kompozisyon B'ye gitmek için çalışmaz, A => B'ye ihtiyacımız vardır, ancak A => F [B] var.
    resim açıklamasını buraya girin

    F [A] döndüren bu fonksiyonların nasıl kaynaşacağını bilen özel bir operatöre, "bağla" ihtiyacımız var.

 A => F[C]   =   A => F[B]  bind  B => F[C]

"Bağlanma" fonksiyonu özgü için tanımlanan F .

Ayrıca, belirli bir F için de tanımlanan herhangi bir A için A => F [A] tipinde "dönüş" vardır . Monad olmak için F'nin bu iki işlevi tanımlaması gerekir.

Böylece bir effectful fonksiyonu gerçekleştirebilmesi A => F [B] bir saf işlevi, A => B ,

 A => F[B]   =   A => B  andThen  return

ancak belirli bir F , aynı zamanda, kullanıcının kendilerini tanımlayamadığı ( saf bir dilde) kendi opak "yerleşik" özel işlevlerini de tanımlayabilir.

  • "random" ( Aralık => Rastgele [Int] )
  • "print" ( Dize => IO [()] )
  • "dene ... yakala" vb.

2

Teorik olarak mükemmel olmayabilir Monads anlayışımı paylaşıyorum. Monadlar Bağlamın yayılmasıyla ilgilidir . Monad, bazı veriler (veya veri türleri) için bir bağlam tanımlarsınız ve daha sonra bu bağlamın işleme hattı boyunca verilerle nasıl taşınacağını tanımlarsınız. Bağlam yayılımını tanımlamak çoğunlukla birden fazla bağlamın (aynı türden) nasıl birleştirileceğini tanımlamakla ilgilidir. Monad'ların kullanılması, bu bağlamların yanlışlıkla verilerden çıkarılmamasını da sağlar. Öte yandan, bağlamsız diğer veriler yeni veya mevcut bir bağlama getirilebilir. Daha sonra bu basit konsept, bir programın derleme zamanı doğruluğunu sağlamak için kullanılabilir.



1

Benim Bkz cevabı için "Ne monad nedir?"

Motive edici bir örnekle başlar, örnek üzerinden çalışır, bir monad örneği oluşturur ve resmi olarak "monad" ı tanımlar.

İşlevsel programlama hakkında hiçbir bilgi sahibi değildir ve function(argument) := expressionmümkün olan en basit ifadelerle sözdizimi içeren sözde kod kullanır .

Bu C ++ programı, sözde kod monadının bir uygulamasıdır. (Referans için: Mtür yapıcısıdır, feed"bağlama" işlemidir ve wrap"dönüş" işlemidir.)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

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

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

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

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

0

Pratik bir bakış açısından (önceki birçok cevapta ve ilgili makalede söylenenleri özetlemek gerekirse), bana göre, monadın temel "amaçlarından" (veya kullanışlılığından) biri, özyinelemeli yöntem çağrılarında örtük bağımlılıkları kullanmaktır. yani işlev kompozisyonu (yani f1, f2, f3'ü çağırdığında, f3'ün f1'den önce f2'den önce değerlendirilmesi gerekir), özellikle tembel bir değerlendirme modeli bağlamında (yani düz bir dizi olarak sıralı kompozisyon) doğal bir şekilde sıralı kompozisyonu temsil etmek için , örn., "f3 (); f2 (); f1 ();" C - hile, f3, f2 ve f1'in aslında hiçbir şey döndürmediği bir durum olduğunu düşünüyorsanız [f1 (f2 (f3)) yapaydır, tamamen dizilim yaratmaya yöneliktir]).

Bu, özellikle yan etkiler söz konusu olduğunda önemlidir, yani bazı durumlar değiştiğinde (f1, f2, f3'ün herhangi bir yan etkisi yoksa, hangi sırayla değerlendirildikleri önemli değildir; fonksiyonel diller, örneğin bu hesaplamaları paralel hale getirme). Daha saf fonksiyonlar, daha iyi.

Bu dar bakış açısından, monadlar, tembel değerlendirmeyi tercih eden diller için sözdizimsel şeker olarak görülebilir (şeyleri sadece kesinlikle gerekli olduğunda değerlendirir, kodun sunumuna dayanmayan bir emri izleyerek) ve hiç sıralı bileşimi temsil etmenin diğer yolları. Net sonuç, "saf olmayan" (yani yan etkileri olan) kod bölümlerinin doğal olarak, zorunlulukla sunulması, ancak saf fonksiyonlardan (yan etkileri olmayan) temiz bir şekilde ayrılabilmesidir. tembel değerlendirdi.

Ancak burada uyarıldığı gibi bu sadece bir veçhedir .


0

Aklıma gelen en basit açıklama, monad'ların işlevleri sonuçlandırılmış (Kleisli kompozisyonu olarak da bilinir) bir şekilde oluşturmanın bir yolu olduğudur. Bir "süslemek için" fonksiyonu imzası a -> (b, smth)nerede ave btürleri (düşünmek vardır Int, Boolbirbirinden farklı olabilir), ama mutlaka - ve smth"bağlam" veya "Embelishment" dir.

Fonksiyonların Bu tip de yazılabilir a -> m bnerede m"Embelishment" eşdeğerdir smth. Bunlar, değerleri bağlam içinde döndüren işlevlerdir (eylemlerini smthkaydeden işlevler, günlük iletisinin nerede olduğu veya giriş \ çıkış gerçekleştiren işlevler ve sonuçları G / Ç işleminin sonucuna bağlıdır).

Monad, uygulayıcının bu işlevleri nasıl oluşturacağını söyleten bir arabirimdir ("typeclass"). Uygulayıcının, arabirimi uygulamak isteyen (a -> m b) -> (b -> m c) -> (a -> m c)herhangi bir tür için bir kompozisyon işlevi tanımlaması gerekir m(bu Kleisli bileşimidir).

Yani, eylemlerini de günlüğe kaydeden (Int, String)hesaplamaların sonuçlarını temsil eden bir tuple tipimiz olduğunu söylesek, "kölelik" - eylemin günlüğü - ve iki fonksiyon olmak ve kompozisyon olan bir fonksiyon elde etmek istiyoruz. günlükleri de dikkate alan iki işlev arasında.Int(_, String)increment :: Int -> (Int, String)twoTimes :: Int -> (Int, String)incrementThenDouble :: Int -> (Int, String)

Verilen örnekte, iki adet fonksiyonları bir tek hücreli uygulama tamsayı değeri 2 için de geçerlidir incrementThenDouble 2(eşittir twoTimes (increment 2)) dönmek (6, " Adding 1. Doubling 3.")aracı sonuçlar increment 2için eşit (3, " Adding 1.")ve twoTimes 3e eşit(6, " Doubling 3.")

Bu Kleisli kompozisyon fonksiyonundan olağan monadik fonksiyonlar türetilebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.