Monad nedir?


1415

Kısa bir süre önce Haskell'e baktığımızda, bir monadın esasen ne olduğu hakkında kısa, özlü ve pratik bir açıklama ne olurdu ?

Karşılaştığım açıklamaların çoğuna erişilemez ve pratik detaylardan yoksun buldum.


12
Eric Lippert bu sorulara bir cevap yazdı ( stackoverflow.com/questions/2704652/… ), bu da bazı sorunların ayrı bir sayfada yaşadığı için yazdı .
P

70
İşte yeni tanıtım kullanarak JavaScript - Ben çok okunabilir bulundu.
Benjol



2
Bir monad, yardımcı işlemleri olan bir dizi işlevdir. Bu cevaba
cibercitizen1

Yanıtlar:


1059

Birincisi: Eğer bir matematikçi değilseniz monad terimi biraz boştur. Alternatif bir terim, aslında ne için yararlı olduklarını biraz daha açıklayan hesaplama oluşturucudur .

Pratik örnekler istersiniz:

Örnek 1: Liste anlama :

[x*2 | x<-[1..10], odd x]

Bu ifade, 1'den 10'a kadar olan tüm tek sayıların çiftlerini döndürür.

Liste monadındaki bazı operasyonlar için bunun sadece sözdizimsel şeker olduğu ortaya çıkıyor. Aynı liste kavrama şu şekilde yazılabilir:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Ya da:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Örnek 2: Giriş / Çıkış :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Her iki örnek de monad, AKA hesaplama kurucuları kullanıyor. Ortak tema, monad'ın operasyonları belirli, kullanışlı bir şekilde zincirlemesidir . Liste kavrayışında, işlemler bir işlem bir liste döndürürse, listedeki her öğede aşağıdaki işlemler gerçekleştirilecek şekilde zincirlenir . IO monad ise işlemleri sırayla gerçekleştirir, ancak "dünyanın durumunu" temsil eden "gizli bir değişkeni" geçer, bu da G / Ç kodunu saf işlevsel bir şekilde yazmamızı sağlar.

Zincirleme operasyonlarının paterninin oldukça yararlı olduğu ortaya çıkıyor ve Haskell'deki birçok farklı şey için kullanılıyor.

Başka bir örnek istisnalardır: ErrorMonad kullanıldığında, işlemler bir hata atılırsa, bu durumda zincirin geri kalanı terk edilirse, sırayla gerçekleştirilecek şekilde zincirlenir.

Hem liste kavrama sözdizimi hem de yapılacaklar >>=operatörü kullanan zincirleme işlemler için sözdizimsel şekerdir . Bir monad temel olarak sadece >>=operatörü destekleyen bir türdür .

Örnek 3: Ayrıştırıcı

Bu, alıntılanmış bir dizeyi veya bir sayıyı ayrıştıran çok basit bir ayrıştırıcıdır:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

İşlemler char, digitvb. Oldukça basittir. Ya eşleşiyor ya da eşleşmiyor. Sihir, kontrol akışını yöneten monaddır: İşlemler bir eşleşme başarısız olana kadar sırayla gerçekleştirilir, bu durumda monad en son geri döner <|>ve bir sonraki seçeneği dener. Yine, bazı ek, kullanışlı semantiklerle işlemleri zincirlemenin bir yolu.

Örnek 4: Zaman uyumsuz programlama

Yukarıdaki örnekler Haskell'de, ancak F #'ın monadları da desteklediği ortaya çıktı . Bu örnek Don Syme'den çalınmıştır :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Bu yöntem bir web sayfası getirir. Delgi çizgisi kullanımıdır GetResponseAsync- ana iş parçacığı işlevden dönerken aslında ayrı bir iş parçacığında yanıt bekler. Son üç satır, yanıt alındığında ortaya çıkan evrede yürütülür.

Diğer birçok dilde, yanıtı işleyen satırlar için açıkça ayrı bir işlev oluşturmanız gerekir. asyncMonad kendi başına bloğu "bölme" yapabiliyor ve ikinci yarısında yürütülmesini ertelemek. ( async {}Sözdizimi, bloktaki kontrol akışının asyncmonad tarafından tanımlandığını gösterir .)

Onlar nasıl çalışır

Peki bir monad tüm bu fantezi kontrol akışını nasıl yapabilir? Gerçekte bir do-block'ta (veya F # olarak adlandırıldıkları gibi bir hesaplama ifadesinde ) olan şey, her işlemin (temelde her satır) ayrı bir anonim işlevle sarılmasıdır. Bu işlevler daha sonra bindoperatör ( >>=Haskell'de yazılır ) kullanılarak birleştirilir . Yana bind, sırayla, birden çok kez ters olmak üzere bazı atmak bunun gibi ve benzeri hissettiğinde ayrı iş parçacığı üzerinde bazı yürütün: uygun gördüğü şekilde işlem birleştirir fonksiyonları, onları yürütebilirsiniz.

Örnek olarak, bu örnek 2'deki IO kodunun genişletilmiş sürümüdür:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Bu daha çirkin, ama gerçekte neler olup bittiği de daha açık. >>=Operatör sihirli maddesi: Bu (sağ tarafta) bir fonksiyonu olan bir (sol tarafta) değeri ve biçerdöverler alır yeni bir değer üretmek. Bu yeni değer daha sonra bir sonraki >>=operatör tarafından alınır ve tekrar yeni bir değer üretmek için bir fonksiyonla birleştirilir. >>=mini değerlendirici olarak görülebilir.

>>=Farklı tipler için aşırı yüklendiğini unutmayın , bu nedenle her monadın kendi uygulaması vardır >>=. (Zincirdeki tüm işlemler aynı monad tipinde olmalıdır, aksi takdirde >>=operatör çalışmaz.)

Mümkün olan en basit uygulama >>=sadece soldaki değeri alır ve sağdaki işleve uygular ve sonucu döndürür, ancak daha önce de belirtildiği gibi, tüm deseni faydalı kılan şey, monad'ın uygulanmasında fazladan bir şey olduğundadır. >>=.

Değerlerin bir işlemden diğerine nasıl aktarıldığına dair bazı ek zekilikler vardır, ancak bu Haskell tipi sistemin daha derin bir açıklamasını gerektirir.

Özetliyor

Haskell terimlerinde bir monad, >>=diğer birkaç işleçle birlikte tanımlayan Monad tip sınıfının bir örneği olan parametreli bir tiptir . Layman'ın terimleriyle, bir monad sadece >>=işlemin tanımlandığı bir türdür .

Kendi başına >>=zincirleme işlevlerinin hantal bir yoludur, ancak "sıhhi tesisat" ı gizleyen do-notasyonun varlığıyla, monadik işlemler çok güzel ve kullanışlı bir soyutlama, dilde birçok yerde yararlı ve kullanışlı dilde kendi mini dillerinizi oluşturmak için.

Monadlar neden zor?

Birçok Haskell öğrenicisi için, monadlar bir tuğla duvar gibi vurdukları bir engeldir. Monadların kendilerinin karmaşık olduğu değil, uygulamanın parametrelenmiş tipler, tip sınıfları vb.Gibi diğer gelişmiş Haskell özelliklerine dayanması. Sorun şu ki, Haskell I / O monadlara dayanıyor ve I / O muhtemelen yeni bir dil öğrenirken anlamak istediğiniz ilk şeylerden biri - sonuçta, herhangi bir üretim yapmayan programlar oluşturmak çok eğlenceli değil çıktı. Bu tavuk-yumurta problemine derhal çözümüm yok, ancak I / O'ya, dilin diğer bölümleriyle yeterince deneyiminiz olana kadar "sihir burada olur" gibi davranmak dışında. Afedersiniz.

Monad'lar hakkında mükemmel blog: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


65
Monadları anlamada çok fazla sorun yaşayan biri olarak bu cevabın biraz yardımcı olduğunu söyleyebilirim. Ancak hala anlamadığım bazı şeyler var. Liste kavraması ne şekilde bir monaddır? Bu örneğin genişletilmiş bir formu var mı? Beni de dahil olmak üzere çoğu monad açıklamaları beni rahatsız eden başka bir şey - onlar sürekli "monad nedir?" "Bir monad ne işe yarar?" ve "Bir monad nasıl uygulanır?". "Bir monad temelde sadece >> = operatörünü destekleyen bir tür" yazdığınızda bu köpekbalığını atladınız. Hangi beni vardı ...
Breton

82
Ayrıca monad'ların neden zor olduğu konusundaki sonucuna katılmıyorum. Monadların kendileri karmaşık değilse, o zaman bir sürü bagaj olmadan ne olduklarını açıklayabilmelisiniz. "Monad nedir" sorusunu sorduğumda uygulama hakkında bilmek istemiyorum, kaşınmanın ne anlama geldiğini bilmek istiyorum. Şimdiye kadar cevap şöyle görünüyor: Çünkü haskell yazarları sadomasochistler ve basit şeyleri başarmak için aptalca karmaşık bir şey yapmanız gerektiğine karar verdiler, bu yüzden herhangi bir şekilde yararlı oldukları için değil, haskell'i kullanacak monadları öğrenmelisiniz kendilerini "...
Breton

69
Ama .. bu doğru olamaz, değil mi? Monadların zor olduğunu düşünüyorum çünkü kimse uygulama ayrıntılarını karıştırmaktan kaçınmadan onları nasıl açıklayacağını anlayamıyor gibi görünüyor. Yani .. okul otobüsü nedir? Önde, bazı metalik pistonları bir döngüde sürmek için rafine edilmiş bir petrol ürünü tüketen ve bazı tekerlekleri çalıştıran bazı dişlilere bağlı bir krank milini döndüren metal bir platform. Tekerlekler, etraflarında bir koltuk koleksiyonunun ilerlemesine neden olmak için bir ashphalt yüzeyiyle arayüz oluşturan şişirilmiş kauçuk torbalara sahiptir. Koltuklar öne doğru hareket ediyor çünkü ...
Breton

130
Tüm bunları okudum ve hala bir monad'ın ne olduğunu bilmiyorum, bunun Haskell programcılarının açıklayabilecek kadar iyi anlamadığı bir şey olmasının yanı sıra. Örnekler, birinin monadlar olmadan yapabileceği her şey olduğu göz önüne alındığında, çok fazla yardımcı olmaz ve bu cevap, monadların onları nasıl daha kolay, sadece daha kafa karıştırıcı hale getirdiğini netleştirmez. Bu cevabın faydalı olmaya yaklaşan bir kısmı, örnek # 2'nin sözdizimsel şekerinin çıkarıldığı yerdi. Diyorum ki yakınlaştım çünkü ilk çizginin yanı sıra genişleme orijinaline gerçek bir benzerlik göstermiyor.
Laurence Gonsalves

81
Monadların açıklamalarına endemik görünen bir diğer sorun ise Haskell'de yazılmış olmasıdır. Haskell'in kötü bir dil olduğunu söylemiyorum - Monad'ları açıklamak için kötü bir dil olduğunu söylüyorum. Eğer Haskell'i zaten monadları anlayacağımı bilseydim, eğer monad'ları açıklamak istiyorsanız, monad'ları bilmeyen insanların anlama olasılığının yüksek olduğu bir dil kullanmaya başlayın. Eğer varsa gerekir yapabilirsiniz dilin en küçük, en basit alt kümesini kullanır ve Haskell IO bir anlayış düşünmeyin - Haskell kullanmak, hiç sözdizimsel şeker kullanmayın.
Laurence Gonsalves

712

"Monad nedir" açıklamak biraz "sayı nedir?" Rakamları her zaman kullanıyoruz. Ama sayılar hakkında hiçbir şey bilmeyen biriyle tanıştığınızı düşünün. Nasıl halt Eğer sayıların ne açıklayabilir? Ve bunun neden faydalı olabileceğini anlatmaya nasıl başlarsın?

Monad nedir? Kısa cevap: Operasyonları birlikte zincirlemenin belirli bir yolu.

Özünde, yürütme adımlarını yazıyorsunuz ve bunları "bağlama fonksiyonu" ile birleştiriyorsunuz. (Haskell'de adı verilir >>=.) Bağlama işlecine aramaları kendiniz yazabilir veya derleyicinin bu işlev çağrılarını sizin için eklemesini sağlayan sözdizimi şekeri kullanabilirsiniz. Ancak her iki durumda da, her adım bu bağlama işlevine yapılan bir çağrı ile ayrılır.

Bağlama işlevi noktalı virgül gibidir; bir işlemdeki adımları ayırır. Bağlama işlevinin işi, çıktıyı önceki adımdan alıp sonraki adıma beslemektir.

Kulağa çok zor gelmiyor, değil mi? Ancak birden fazla tür monad var. Neden? Nasıl?

Eh, bağlama işlevi olabilir sadece bir adım sonucunu almak ve bir sonraki adıma besleyin. Ama eğer monad'ın yaptığı "hepsi" ise ... bu aslında pek kullanışlı değil. Ve bunu anlamak önemlidir: Her faydalı monad sadece bir monad olmanın yanı sıra başka bir şey yapar . Her kullanışlı monad, onu benzersiz kılan "özel bir güce" sahiptir.

(Özel bir şey yapmayan bir monad'a "kimlik monadı" denir. Kimlik işlevi gibi, bu tamamen anlamsız bir şey gibi geliyor, ama öyle değil ... Ama bu başka bir hikaye ™.)

Temel olarak, her monad'ın bağlama fonksiyonu için kendi uygulaması vardır. Ve bir bağlama işlevi yazabilirsiniz, böylece yürütme adımları arasında bir şeyler olur. Örneğin:

  • Her adım bir başarı / başarısızlık göstergesi döndürürse, bir sonraki adımı ancak bir önceki adım başarılı olduğunda yürüttürebilirsiniz. Bu şekilde, başarısız bir adım sizden herhangi bir koşullu test yapmadan tüm diziyi "otomatik olarak" iptal eder. ( Başarısızlık Monad .)

  • Bu fikri genişleterek "istisnalar" uygulayabilirsiniz. ( Hata Monad veya İstisna Monad .) Dil özelliği olmak yerine bunları kendiniz tanımladığınız için nasıl çalışacaklarını tanımlayabilirsiniz. (Örneğin, belki de ilk iki istisnayı yok saymak ve yalnızca üçüncü bir istisna atıldığında iptal etmek istersiniz .)

  • Her adımın birden çok sonuç döndürmesini sağlayabilir ve her biri sizin için bir sonraki adıma besleyerek bağlama işlevi döngüsünün üzerine gelmesini sağlayabilirsiniz . Bu şekilde, birden çok sonuçla uğraşırken her yerde döngü yazmaya devam etmenize gerek kalmaz. Bağlama işlevi "otomatik olarak" sizin için her şeyi yapar. ( Liste Monad .)

  • Bir adımdan diğerine bir "sonuç" iletmenin yanı sıra, bağlama işlevinin de fazladan veri iletmesini sağlayabilirsiniz . Bu veriler artık kaynak kodunuzda görünmüyor, ancak yine de her işleve manuel olarak aktarmak zorunda kalmadan yine de her yerden erişebilirsiniz. ( Okuyucu Monad .)

  • "Ek veri" nin değiştirilebilmesi için bunu yapabilirsiniz. Bu, gerçekten yıkıcı güncellemeler yapmadan yıkıcı güncellemeleri simüle etmenizi sağlar . ( Devlet Monad ve kuzeni Yazar Monad .)

  • Sadece yıkıcı güncellemeleri simüle ettiğiniz için, gerçek yıkıcı güncellemelerle imkansız olan şeyleri önemsiz bir şekilde yapabilirsiniz . Örneğin , son güncellemeyi geri alabilir veya daha eski bir sürüme geri dönebilirsiniz .

  • Hesaplamaların duraklatılabileceği bir monad oluşturabilirsiniz , böylece programınızı duraklatabilir, içeri girebilir ve dahili durum verileriyle uğraşabilir ve sonra devam ettirebilirsiniz.

  • "Devamları" bir monad olarak uygulayabilirsiniz. Bu, insanların zihinlerini kırmanıza izin verir !

Tüm bunlar ve daha fazlası monadlarla mümkündür. Tabii ki, bunların hepsi monadlar olmadan da mükemmel bir şekilde mümkündür . Monad kullanmak son derece kolaydır .


13
Cevabınızı takdir ediyorum - özellikle de bunların hepsinin elbette monadlar olmadan da mümkün olduğu son imtiyazı. Belirtilmesi gereken bir nokta , monadlarla daha kolay olduğu, ancak genellikle onlarsız yapmak kadar verimli olmadığıdır. Transformatörleri dahil etmeniz gerektiğinde, işlev çağrılarının (ve oluşturulan işlev nesnelerinin) ekstra katmanının görülmesi ve kontrolü zor olan ve akıllı sözdizimi tarafından görünmez hale gelen bir maliyeti vardır.
seh

1
En azından Haskell'de, monadların genel giderlerinin çoğu iyileştirici tarafından soyuluyor. Yani tek gerçek “maliyet” gerekli beyin gücünde. ("Sürdürülebilirlik" önem verdiğiniz bir şeyse bu önemsiz değildir.) Ama genellikle, monadlar işleri kolaylaştırır , zorlaştırmaz. (Aksi halde neden rahatsız edersin?)
MathematicalOrchid

Haskell'in bunu destekleyip desteklemediğinden emin değilim ama matematiksel olarak >> = cinsinden bir monad tanımlayabilir ve return veya join ve ap. >> = ve dönüş, monad'ları pratik olarak yararlı kılan şeydir, ancak katılmak ve ap, bir monadın ne olduğu hakkında daha sezgisel bir anlayış sağlar.
Jeremy List

15
Matematiksel olmayan, fonksiyonel olmayan bir programlama geçmişinden gelen bu cevap benim için en anlamlı.
jrahhali

10
Bu bana bir monadın ne olduğu hakkında biraz fikir veren ilk cevap. Bunu açıklamanın bir yolunu bulduğunuz için teşekkür ederiz!
robot Mayıs

186

Aslında, Monadların ortak anlayışının aksine, devletle hiçbir ilgileri yoktur. Monadlar, sadece bir şeyleri sarmanın ve sarılmış şeyleri açmadan işlem yapmak için yöntemler sağlamanın bir yoludur.

Örneğin, Haskell'de başka birini sarmak için bir tür oluşturabilirsiniz:

data Wrapped a = Wrap a

Tanımlamak istediğimiz şeyleri

return :: a -> Wrapped a
return x = Wrap x

İşlemleri açmadan gerçekleştirmek için, bir işleviniz olduğunu varsayalım, f :: a -> bo zaman bu işlevi sarılmış değerlere göre hareket etmek için kaldırabilirsiniz :

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Anlamak için gereken her şey bu. Bununla birlikte, bu kaldırmayı yapmak için daha genel bir fonksiyon olduğu ortaya çıkıyor , yani bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindbiraz daha fazlasını yapabilir fmap, ancak tam tersi olamaz. Aslında, fmapsadece açısından tanımlanabilir bindve return. Yani, bir monad tanımlarken .. onun türünü (burada Wrapped a) ve sonra onun returnve bindoperasyonların nasıl çalıştığını söylersiniz .

Harika olan şey, bunun her yerde ortaya çıkacağı kadar genel bir kalıp olduğu, saf bir şekilde kapsül haline getirme durumlarından sadece bir tanesidir.

Monads fonksiyonel bağımlılıkları tanıtmak ve böylece Monad Haskell'ın IO kullanılır gibi, değerlendirme sırasını kontrol kontrol etmek nasıl kullanılabileceği üzerinde iyi makale için IO İçine .

Monad'ları anlamaya gelince, bu konuda fazla endişelenme. İlginç bulduğunuz şeyleri okuyun ve hemen anlamadıysanız endişelenmeyin. O zaman sadece Haskell gibi bir dilde dalış yapmanın yolu. Monadlar, pratik yaparak beyninize anlamanın damlama yaptığı şeylerden biridir, bir gün aniden onları anladığınızı fark edersiniz.


-> sağ çağrışımsaldır, sol çağrışımsal olan yansıtma işlevi uygulamasıdır, bu nedenle parantezleri dışarıda bırakmak burada bir fark yaratmaz.
Matthias Benkard

1
Bunun çok iyi bir açıklama olduğunu sanmıyorum. Monadlar basitçe bir yol mu? tamam, hangi yönden? Neden bir monad yerine sınıf kullanarak kapsülleme yapmıyorum?
Breton

4
@ mb21: Sadece çok fazla köşeli ayraç bulunduğuna işaret ediyorsanız, a-> b-> c'nin aslında bir -> (b-> c) için kısa olduğunu unutmayın. Bu özel örneği (a -> b) -> (Ta -> Tb) olarak yazmak kesinlikle gereksiz karakterler eklemekle ilgilidir, ancak fmap'ın a -> türündeki bir işlevi eşlediğini vurguladığı için ahlaki olarak "yapılacak doğru şey" dir. b Ta -> Tb türündeki bir işleve. Ve aslında, kategori teorisinde functorlar bunu yapar ve monadların geldiği yer budur.
Nikolaj-K

1
Bu cevap yanıltıcı. Bazı monadlarda "sarıcı" yoktur, böyle bir fonksiyon sabit bir değerdedir.

1
@DanMandel Monads, kendi veri tipi ambalajını sağlayan tasarım kalıplarıdır. Monadlar, kazan plakası kodunu soyutlayacak şekilde tasarlanmıştır. Kodunuzda bir Monad dediğinizde, endişelenmek istemediğiniz sahnelerin arkasında. Nullable <T> veya IEnumerable <T> düşünün, perde arkasında ne yapıyorlar? Bu Monad.
sksallaj

168

Ama Monadları icat etmiş olabilirsiniz!

sigfpe diyor ki:

Fakat bunların hepsi monadları açıklamaya ihtiyaç duyan ezoterik bir şey olarak tanımlıyor. Ama tartışmak istediğim şey, ezoterik olmamaları. Aslında, fonksiyonel programlamada çeşitli sorunlarla karşılaşırsanız, kaçınılmaz olarak, hepsi monad örnekleri olan belirli çözümlere yönlendirilirdiniz. Aslında, henüz yapmadıysanız onları icat etmenizi umuyorum. Daha sonra, tüm bu çözümlerin aslında kılık değiştirmede aynı çözüm olduğunu fark etmek için küçük bir adımdır. Ve bunu okuduktan sonra, monad'lardaki diğer belgeleri anlamak için daha iyi bir konumda olabilirsiniz, çünkü gördüğünüz her şeyi zaten icat ettiğiniz bir şey olarak tanıyacaksınız.

Monadların çözmeye çalıştığı sorunların çoğu yan etkilerle ilgilidir. Böylece onlarla başlayacağız. (Monad'ların yan etkilerden daha fazlasını yapmanıza izin verdiğini, özellikle de birçok konteyner nesnesi monad olarak görülebilir. diğer.)

C ++ gibi zorunlu bir programlama dilinde, fonksiyonlar matematiğin fonksiyonları gibi davranmaz. Örneğin, tek bir kayan nokta bağımsız değişkeni alan ve bir kayan nokta sonucu döndüren bir C ++ işlevimiz olduğunu varsayalım. Yüzeysel olarak gerçekleri gerçeklerle eşleştiren bir matematiksel fonksiyon gibi görünebilir, ancak bir C ++ fonksiyonu sadece argümanlarına bağlı bir sayı döndürmekten daha fazlasını yapabilir. Küresel değişkenlerin değerlerini okuyabilir ve yazabilir, ayrıca ekrana çıktı yazabilir ve kullanıcıdan girdi alabilir. Bununla birlikte, saf işlevsel bir dilde, bir işlev yalnızca argümanlarında kendisine verilenleri okuyabilir ve dünya üzerinde bir etkisi olabilmesinin tek yolu, döndürdüğü değerlerdir.


9
… Sadece internette değil, her yerde en iyi yol. (Wadler'in orijinal makalesi Aşağıdaki cevabımda bahsettiğim işlevsel programlama için Monad'lar da iyi.) Analoji derslerinin hiçbiri yaklaşmıyor.
ShreevatsaR

13
Sigfpe'un gönderisinin bu JavaScript çevirisi, zaten gelişmiş Haskell'i bilmeyen insanlar için monadları öğrenmenin yeni en iyi yoludur!
Sam Watkins

1
Bir monadın ne olduğunu bu şekilde öğrendim. Okuyucuyu bir kavram icat etme sürecinde yürümek genellikle kavramı öğretmenin en iyi yoludur.
Ürdün

Ancak, ekran nesnesini bağımsız değişken olarak kabul eden ve kopyasını değiştirilmiş metinle döndüren bir işlev saf olacaktır.
Dmitri Zaitsev

87

Monad, iki işlemi olan bir veri tipidir: >>=(aka bind) ve return(aka unit). returnisteğe bağlı bir değer alır ve onunla birlikte monad'ın bir örneğini oluşturur. >>=monad'ın bir örneğini alır ve üzerinde bir işlev eşleştirir. (Zaten bir monad'ın garip bir veri türü olduğunu görebilirsiniz, çünkü çoğu programlama dilinde keyfi bir değer alan ve ondan bir tür yaratan bir işlev yazamazsınız. Monadlar bir tür parametrik polimorfizm kullanırlar .)

Haskell gösteriminde, monad arayüzü yazılmıştır

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Bu operasyonların belirli "yasalara" uyması gerekiyordu, ama bu çok önemli değil: "yasalar" sadece operasyonların mantıklı uygulamalarının nasıl davranması gerektiğini (temelde, >>=ve returndeğerlerin monad örneklerine nasıl dönüştüğü konusunda hemfikir olmalı ve bu >>=), birleştirici bir.

Monadlar sadece devlet ve I / O ile ilgili değildir: devlet, I / O, istisnalar ve determinizm ile çalışmayı içeren ortak bir hesaplama modeli oluştururlar. Muhtemelen anlaşılması en kolay monadlar listeler ve seçenek türleridir:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

nerede []ve :liste kurucular, ++birleştirme operatörü olduğunu ve Justve Nothingolan Maybekurucular. Bu monadların her ikisi de, kendi veri türleri üzerinde ortak ve kullanışlı hesaplama modellerini kapsamaktadır (hiçbirinin yan etkiler veya G / Ç ile hiçbir ilgisi olmadığını unutmayın).

Monad'ların ne hakkında olduğunu ve neden yararlı olduklarını takdir etmek için önemsiz olmayan Haskell kodları yazarak gerçekten oynamak zorundasınız.


"Bir işlevi onun üzerinde haritalar" ile tam olarak ne demek istiyorsun?
Casebash

Casebash, girişte kasıtlı olarak gayrı resmi oluyorum. "Bir fonksiyonun haritalanması" nın ne anlama geldiğini anlamak için sondaki örneklere bakın.
Chris Conway

3
Monad bir veri türü değildir. İşlevleri oluşturma kuralıdır: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev haklı, Monads aslında kendi veri veri türünü sağlıyor, Monads arent veri türleri
sksallaj

78

Önce bir fonksiyonun ne olduğunu anlamalısınız. Bundan önce, üst düzey fonksiyonları anlayın.

Bir yüksek dereceden fonksiyonu sadece bir değişken olarak bir fonksiyon alan bir fonksiyondur.

Bir funktor her tür inşaat Tdaha yüksek seviyeli bir fonksiyonu vardır için de çağrı map, bu dönüşümü tipinin bir fonksiyonu a -> b(iki tür verilmiş ave bbir işlev) T a -> T b. Bu mapişlev aynı zamanda kimlik ve kompozisyon yasalarına uymalıdır, böylece aşağıdaki ifadeler herkes için geçerli olur pve q(Haskell notasyonu):

map id = id
map (p . q) = map p . map q

Örneğin, yukarıdaki yasalara uyan Listtürden bir işlevle donatılmışsa, çağrılan bir tür yapıcı işlevdir (a -> b) -> List a -> List b. Tek pratik uygulama açıktır. Ortaya çıkan List a -> List bişlev, (a -> b)her bir öğe için işlevi çağırarak verilen liste üzerinde yinelenir ve sonuçların listesini döndürür.

Bir monad esasen sadece funktor olan Tiki ekstra yöntemlerle, join, tip T (T a) -> T ave unit(bazen return, forkya da puretürden) a -> T a. Haskell'deki listeler için:

join :: [[a]] -> [a]
pure :: a -> [a]

Bu neden faydalı? Çünkü, örneğin, mapbir liste döndüren bir işleve sahip bir liste üzerinden yapabilirsiniz. Joinsonuçta ortaya çıkan liste listesini alır ve sıralar. Listbir monad çünkü bu mümkün.

Beni anlayan bir fonksiyon yazabiliriz mapsonra join. Bu fonksiyon denir bindya flatMap, ya (>>=), ya (=<<). Haskell'de normalde böyle bir monad örneği verilir.

Bir monad, belirli yasaları yerine getirmek joinzorundadır , yani çağrışımsal olmalıdır. Bir değer varsa o Bu araçlar xÇeşidi [[[a]]]sonra join (join x)eşit olmalıdır join (map join x). Ve böyle purebir kimlik olmalı .joinjoin (pure x) == x


3
'yüksek dereceli işlev' def için hafif bir ekleme: VEYA DÖNÜŞ işlevlerini alabilirler. Bu yüzden kendileriyle bir şeyler yaptıkları için 'daha yüksek'.
Kevin Won

9
Bu tanıma göre toplama, daha üst düzey bir işlevdir. Bir sayı alır ve bu sayıyı diğerine ekleyen bir işlev döndürür. Bu nedenle, hayır, daha üst düzey işlevler, etki alanı işlevlerden oluşan işlevlerdir.
Apocalisp

' Brian Beckman: Monad'dan korkma ' videosu da aynı mantığı takip ediyor.
icc97

48

[Feragatname: Hala monad'ları tamamen grok yapmaya çalışıyorum. Şimdiye kadar anladığım şu. Eğer yanlışsa, umarım bilgili biri beni halıda arayacaktır.]

Arnar şunu yazdı:

Monadlar, sadece bir şeyleri sarmanın ve sarılmış şeyleri açmadan işlem yapmak için yöntemler sağlamanın bir yoludur.

Aynen öyle. Fikir şöyle:

  1. Bir çeşit değer alırsınız ve bazı ek bilgilerle sararsınız. Değer belirli bir türdeyse (örn. Bir tamsayı veya bir dize) olduğu gibi, ek bilgiler de belirli bir türdedir.

    Örneğin, bu ekstra bilgi bir Maybeveya bir olabilir IO.

  2. Ardından, bu ek bilgileri taşırken sarılmış veriler üzerinde çalışmanıza izin veren bazı operatörleriniz var. Bu işleçler, sarılmış değer üzerindeki işlemin davranışını nasıl değiştireceğine karar vermek için ek bilgileri kullanır.

    Örneğin, bir Maybe Intbir olabilir Just Intya Nothing. Artık bir eklerseniz, Maybe Intbir karşı Maybe Int, operatör ikisi de olup olmadığını görmek için kontrol edecektir Just Intiçerde, ve eğer öyleyse, ambalajından edecek Int, s onları toplama operatörünü geçmesi, elde yeniden sarın Intyeni içine Just Intgeçerli olan ( Maybe Int) ve dolayısıyla a Maybe Int. Ama eğer Nothingiçlerinden biri içerideyse, bu operatör hemen geri dönecektir Nothing, ki bu yine geçerlidir Maybe Int. Bu şekilde, s'nizin Maybe Intnormal sayılar olduğunu iddia edebilir ve bunlar üzerinde düzenli matematik yapabilirsiniz. Eğer bir tane alacak olsaydınız Nothing, denklemleriniz her yerde kontrolleri yapmak zorunda kalmadanNothing yine de doğru sonucu verecektir .

Ama örnek tam da bunun için Maybe. Ek bilgi bir olsaydı, bunun yerine s IOiçin tanımlanan özel işleç IOçağrılır ve ekleme yapılmadan önce tamamen farklı bir şey yapabilirdi. (Tamam, iki IO Ints'yi birlikte eklemek muhtemelen saçmadır - henüz emin değilim.) (Ayrıca, Maybeörneğe dikkat ettiyseniz, “bir değeri fazladan şeylerle sarmanın” her zaman doğru olmadığını fark ettiniz. Ama zor anlaşılmaz olmadan kesin, doğru ve kesin olmak.)

Temel olarak, “monad” kabaca “desen” anlamına gelir . Ancak gayri resmi olarak açıklanmış ve özel olarak adlandırılmış Desenlerle dolu bir kitap yerine, artık yeni desenleri programınızdaki şeyler olarak bildirmenize izin veren bir dil yapınız - sözdizimi ve hepsi - var . (Buradaki kesinsizlik, tüm kalıpların belirli bir formu takip etmesi gerektiğidir, bu yüzden bir monad bir kalıp kadar genel değildir. Ama bence bu çoğu insanın bildiği ve anladığı en yakın terimdir.)

İşte bu yüzden insanlar monad'ları bu kadar kafa karıştırıcı buluyor: çünkü onlar böyle genel bir kavram. Bir şeyi bir monad yapan şeyin ne olduğunu sormak, bir şeyi neyin bir model haline getirdiğini sormak gibi belirsizdir.

Ancak, bir örüntü fikri için dilde sözdizimsel desteğe sahip olmanın sonuçlarını düşünün: Dörtlü Çete kitabını okumak ve belirli bir örüntünün yapısını ezberlemek yerine, bu örüntüyü agnostik olarak uygulayan kodu yazmanız yeterlidir, Genel yol bir kez ve sonra bitti! Daha sonra Ziyaretçi veya Strateji veya Cephe gibi her şeyi tekrar tekrar uygulamanıza gerek kalmadan kodunuzdaki işlemleri onunla süsleyerek yeniden kullanabilirsiniz!

Bu yüzden monad'ları anlayan insanlar onları çok yararlı buluyor : entelektüel snobsların kendilerini anlama konusunda gurur duydukları bir fildişi kule konsepti değil (Tamam, elbette teehee), ama aslında kodu daha basit hale getiriyor.


12
Bazen bir "öğrenen" den (sizin gibi) bir açıklama, bir uzmandan gelen bir açıklamadan başka bir öğrenenle daha alakalı olur. Öğrenenler aynı düşünüyor :)
Adrian

Bir şeyi monad yapan şey, tipli bir fonksiyonun varlığıdır M (M a) -> M a. Bunu bir tür M a -> (a -> M b) -> M bhaline getirebilmeniz, onları kullanışlı kılan şeydir.
Jeremy List

"monad" kabaca "desen" anlamına gelir ... hayır.
Teşekkürler

44

Çok çabaladıktan sonra, sanırım sonunda monad'ı anlıyorum. En çok oy alan cevabın kendi uzun eleştirisini tekrar okuduktan sonra bu açıklamayı sunacağım.

Monad'ları anlamak için cevaplanması gereken üç soru var:

  1. Neden bir monad'a ihtiyacın var?
  2. Monad nedir?
  3. Bir monad nasıl uygulanır?

Orijinal yorumlarımda belirttiğim gibi, çok sayıda monad açıklaması 3. soruya veya 2. soruya yeterince cevap vermeden ve gerçekten yeterince kapsamadan önce 3. soruya takılır.

Neden bir monad'a ihtiyacın var?

Haskell gibi saf fonksiyonel diller, C veya Java gibi zorunlu dillerden farklıdır, çünkü saf fonksiyonel bir program, belirli bir sırada, her seferinde bir adım olmak zorunda değildir. Haskell programı, "denklemi" herhangi bir sayıda potansiyel sırada çözebileceğiniz matematiksel bir işleve daha çok benzemektedir. Bu, bir dizi fayda sağlar, aralarında belirli tür hataların, özellikle "devlet" gibi şeylerle ilgili olanların olasılığını ortadan kaldırmasıdır.

Ancak, bu programlama tarzı ile çözülmesi çok kolay olmayan bazı problemler vardır. Konsol programlama ve dosya giriş / çıkış gibi bazı şeylerin belirli bir sırada gerçekleşmesi veya durumunun korunması gerekir. Bu sorunla başa çıkmanın bir yolu, bir hesaplama durumunu temsil eden bir tür nesne ve bir durum nesnesini girdi olarak alan ve yeni bir değiştirilmiş durum nesnesi döndüren bir dizi işlev oluşturmaktır.

Şimdi konsol ekranının durumunu temsil eden varsayımsal bir "durum" değeri yaratalım. tam olarak bu değerin nasıl oluşturulduğu önemli değildir, ancak diyelim ki ekranda görünenleri temsil eden bayt uzunluktaki ascii karakterleri dizisi ve kullanıcı tarafından girilen son giriş satırını temsil eden bir dizi sözde kodudur. Konsol durumunu alan, değiştiren ve yeni bir konsol durumu döndüren bazı işlevler tanımladık.

consolestate MyConsole = new consolestate;

Konsol programlama yapmak için, ancak saf işlevsel bir şekilde, birbirinin içine birçok işlev çağrısı yerleştirmeniz gerekir.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

Bu şekilde programlama, "saf" işlevsel stili korurken, konsoldaki değişiklikleri belirli bir sırada olmaya zorlar. Ancak, muhtemelen yukarıdaki örnekteki gibi bir seferde sadece birkaç işlemden fazlasını yapmak isteyeceğiz. Yuvalama işlevleri bu şekilde kaba olmaya başlayacaktır. İstediğimiz, aslında yukarıdakiyle aynı şeyi yapan, ancak biraz daha böyle yazılan kod:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Bu gerçekten yazmanın daha uygun bir yolu olurdu. Bunu nasıl yaparız?

Monad nedir?

consolestateBu tür üzerinde çalışmak üzere özel olarak tasarlanmış bir grup işlevle birlikte tanımladığınız bir türe (örneğin ) sahip olduğunuzda :, otomatik olarak bu tür (bind) gibi bir operatör tanımlayarak bu paketlerin tümünü "monad" a dönüştürebilirsiniz. soldaki dönüş değerlerini, sağdaki işlev parametrelerine ve liftnormal işlevleri dönüştüren bir işleç, bu tür bir bağlayıcı işleçle çalışan işlevlere besler .

Bir monad nasıl uygulanır?

Bunun ayrıntılarına atlamak oldukça özgür gibi görünen diğer cevaplara bakın.


Bir monad tanımlamanın tek nedeni sıralama değildir. Bir monad sadece bağlanan ve geri dönen herhangi bir işlevcidir. Bağlama ve geri dönüş size sıralama sağlar. Ama başka şeyler de veriyorlar. Ayrıca, en sevdiğiniz zorunlu dilin etkili bir şekilde OO sınıflarına sahip süslü bir IO monad olduğunu unutmayın. Monadları tanımlamayı kolaylaştırmak, tercüman desenini kullanmanın kolay olduğu anlamına gelir - bir DSL'i monad olarak tanımlayın ve yorumlayın!
nomen


38

Birkaç yıl önce bu soruya bir cevap verdikten sonra, bu yanıtı geliştirip basitleştirebileceğime inanıyorum ...

Bir monad, kompozisyon bindsırasında girişi önceden işlemek için bir oluşturma fonksiyonu kullanarak bazı girdi senaryoları için tedaviyi dışsallaştıran bir fonksiyon kompozisyon tekniğidir .

Normal bileşimde, işlev, compose (>>)oluşturulan işlevi selefinin sonucuna sırayla uygulamak için kullanılır. Daha da önemlisi, oluşturulan işlev, girdisinin tüm senaryolarını işlemek için gereklidir.

(x -> y) >> (y -> z)

Bu tasarım, girdinin yeniden yapılandırılmasıyla iyileştirilebilir, böylece ilgili durumlar daha kolay sorgulanabilir. Yani, sadece ydeğer yerine, Mbörneğin bir geçerlilik nosyonu dahil (is_OK, b)edilirse , böyle olabilir y.

Örneğin, girdi yalnızca bir sayı olduğunda, bir sayıyı içeren ya da içermeyen bir dizeyi döndürmek yerine, türü boolgeçerli bir sayının varlığını ve tupledaki bir sayının varlığını gösteren bir şekilde yeniden yapılandırabilirsiniz bool * float. Oluşturulan işlevlerin artık bir sayının var olup olmadığını belirlemek için bir girdi dizesini ayrıştırması gerekmeyecek, ancak sadece boolbir demetin bölümünü inceleyebilecektir .

(Ma -> Mb) >> (Mb -> Mc)

Burada yine, kompozisyon doğal olarak gerçekleşir composeve bu nedenle her fonksiyon, girdisinin tüm senaryolarını ayrı ayrı ele almalıdır, ancak bunu yapmak artık çok daha kolaydır.

Ancak, bir senaryonun ele alınmasının rutin olduğu zamanlar için sorgulama çabalarını dışsallaştırabilseydik. Örneğin, girişimiz olduğu gibi olmadığı zaman programımız hiçbir şey yapmazsa ne is_OKolur false? Bu yapıldıysa, oluşturulan işlevlerin bu senaryoyu kendileri ele alması gerekmez, kodlarını önemli ölçüde basitleştirir ve başka bir yeniden kullanım düzeyini etkiler.

Bu dışsallaştırmayı başarmak bind (>>=)için compositionyerine bir işlev kullanabiliriz compose. Bu nedenle, değerleri bir fonksiyonun çıkışından diğerinin girişine aktarmak yerine, bir kısmını Bindinceleyecek ve oluşturulan fonksiyonun . Tabii ki, fonksiyon , yapısını inceleyebilmek ve istediğimiz her türlü uygulamayı yapabilmek için özel olarak tanımlanmış olacaktır . Bununla birlikte, herhangi bir şey olabilir , çünkü gerekli uygulamayı belirlediğinde, yalnızca beklenmedik olanı oluşturulan işleve geçirir . Ek olarak, oluşturulan işlevlerin kendileri artıkMMaabindMabindaMya da giriş yapısının bir kısmını basitleştirerek. Dolayısıyla ...

(a -> Mb) >>= (b -> Mc) veya daha az özlü Mb >>= (b -> Mc)

Kısacası, bir monad, girdi onları yeterince açığa çıkaracak şekilde tasarlandığında belirli girdi senaryolarının tedavisi konusunda dışsallaşır ve böylece standart davranış sağlar. Bu tasarım, shell and contentkabuğun, oluşturulan işlevin uygulanmasıyla ilgili veriler içerdiği ve yalnızca bindişlev tarafından sorgulandığı ve yalnızca işlev tarafından kullanılabilir kaldığı bir modeldir .

Bu nedenle, bir monad üç şeydir:

  1. Mmonad ile ilgili bilgileri tutmak için bir kabuk,
  2. bindbu kabuk bilgisinden, oluşturulan fonksiyonları kabuk içinde bulduğu içerik değer (ler) ine uygulanmasında kullanmak için uygulanan bir fonksiyon ve
  3. formun birleştirilebilir işlevleri, a -> Mbmonadik yönetim verilerini içeren sonuçlar üretilmesi.

Genel olarak konuşursak, bir fonksiyonun girdisi çıktısından çok daha kısıtlayıcıdır; bu hata koşulları; dolayısıyla Mbsonuç yapısı genellikle çok faydalıdır. Örneğin, bölme işleci bölen olduğunda bir sayı döndürmez 0.

Ek olarak monads, değerleri auygulamadan sonra sararak değerleri, monadik tipe Mave genel fonksiyonları a -> bmonadik fonksiyonlara saran sarma fonksiyonlarını içerebilir a -> Mb. Elbette bind, bu gibi sarma işlevlerine özgüdür M. Bir örnek:

let return a = [a]
let lift f a = return (f a)

bindFonksiyonun tasarımı değişmez veri yapıları ve diğerlerinin karmaşıklaştığı saf fonksiyonlar olduğunu varsayar ve garantiler yapılamaz. Bu nedenle, monadik yasalar vardır:

Verilen ...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Sonra...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitybindne zaman binduygulanırsa uygulansın değerlendirme sırasını koruyan anlamına gelir . Olduğunu, tanımında Associativityparantez yürürlüğe yukarıda erken değerlendirmenin bindingait fve gsadece beklediği bir işleve neden olacaktır Masırayla tamamlamak için bind. Dolayısıyla, Madeğerinin uygulanabilmesi fve sonuçta uygulanabilmesi için değerlendirilmesi yapılmalıdır g.


"... ama umarım diğerleri yararlı buluyorum" tüm vurgulanan cümlelere rağmen, gerçekten benim için yararlı oldu: D

Bu şimdiye kadar okuduğum / izlediğim / duyduğum monad'ların en özlü ve net açıklamasıdır. Teşekkür ederim!
James

Monad ve Monoid arasında önemli bir fark vardır. Monad, farklı tipler arasında fonksiyonları "oluşturmak" için bir kuraldır , bu nedenle Monoidler için gerektiği gibi ikili bir işlem oluşturmazlar, daha fazla ayrıntı için buraya bakın: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Evet. Haklısın. Makalen başımın üstündeydi :). Ancak, bu tedaviyi çok yararlı buldum (ve başkalarına bir yön olarak benimkine ekledim). Başınız için teşekkürler: stackoverflow.com/a/7829607/1612190
George

2
Cebirsel grup teorisini Monad'ın geldiği Kategori teorisi ile karıştırmış olabilirsiniz . Birincisi ilgisiz olan cebirsel gruplar teorisidir.
Dmitri Zaitsev

37

Bir monad, etkili bir şekilde "tip operatör" dür. Üç şey yapacak. İlk olarak, bir türdeki bir değeri başka bir türe "(tipik olarak" monadik tip "olarak adlandırılır)" sarar (veya başka türlü dönüştürür). İkinci olarak, temel türdeki tüm işlemleri (veya işlevleri) monadik tipte kullanılabilir hale getirecektir. Son olarak, bileşik bir monad üretmek için kendini başka bir monad ile birleştirmek için destek sağlayacaktır.

"Belki de monad" aslında Visual Basic / C # "nullable türleri" eşdeğerdir. Null olmayan bir tür "T" alır ve bunu bir "Nullable <T>" biçimine dönüştürür ve sonra Nullable <T> üzerindeki tüm ikili işleçlerin ne anlama geldiğini tanımlar.

Yan etkiler benzer şekilde temsil edilir. Bir işlevin dönüş değerinin yanında yan etkilerin açıklamalarını tutan bir yapı oluşturulur. "Kaldırılmış" işlemler daha sonra değerler fonksiyonlar arasında iletildikçe yan etkiler etrafına kopyalanır.

Birkaç nedenden dolayı "tip operatörler" i kavramak daha kolay değil, "monad" olarak adlandırılırlar:

  1. Monadların yapabilecekleri konusunda kısıtlamaları vardır (ayrıntılar için tanımlamaya bakın).
  2. Bu kısıtlamalar, üç işlemin olması gerçeğiyle birlikte, matematiğin belirsiz bir dalı olan Kategori Teorisinde monad olarak adlandırılan bir şeyin yapısına uygundur.
  3. "Saf" işlevsel dillerin destekçileri tarafından tasarlandılar
  4. Matematiğin belirsiz dalları gibi saf işlevsel dillerin savunucuları
  5. Matematik belirsiz ve monadlar belirli programlama stilleri ile ilişkili olduğundan, insanlar monad kelimesini bir tür gizli el sıkışma olarak kullanma eğilimindedir. Bu nedenle hiç kimse daha iyi bir isme yatırım yapmakla uğraşmadı.

1
Monadlar 'tasarlanmadı', bir alandan (kategori teorisi) diğerine (tamamen işlevsel programlama dillerinde I / O) uygulandılar. Newton hesabı hesapladı mı?
Jared Updike

1
Yukarıdaki 1. ve 2. maddeler doğrudur ve kullanışlıdır. Nokta 4 ve 5, az ya da çok doğru olsa bile, bir tür ad hominemidir. Monad'ları açıklamaya gerçekten yardımcı olmuyorlar.
Jared Updike

13
Re: 4, 5: "Gizli el sıkışma" şey kırmızı bir ringa balığı. Programlama jargon ile doludur. Haskell, bir şeyleri yeniden keşfetmiş gibi davranmadan bir şeyleri çağırıyor. Matematikte zaten varsa, neden yeni bir isim oluşturuyorsunuz? Adı gerçekten insanların monad almamalarının nedeni değildir; ince bir kavramdır. Ortalama bir kişi muhtemelen toplama ve çarpmayı anlar, neden Abelian Grubu kavramını alamıyorlar? Çünkü daha soyut ve geneldir ve o kişi başını kavramın etrafına sarmak için çalışma yapmamıştır. Bir isim değişikliği yardımcı olmaz.
Jared Updike

16
Ah ... Haskell'e saldırmıyorum ... Şaka yapıyordum. Yani, "ad hominem" olma konusunda pek bir şey anlamadım. Evet, hesap "tasarlandı". Bu nedenle, örneğin, matematik öğrencilerine Netwton'un kullandığı icky şeyler yerine Leibniz notasyonu öğretilir. Daha iyi tasarım. İyi isimler çok şey anlamaya yardımcı olur. Abelian Gruplarına "şişmiş kırışıklık kapsülleri" adını verirsem, beni anlamakta güçlük çekebilirsin. "Ama bu isim saçmalık" diyor olabilirsiniz, hiç kimse onlara böyle demez. "Monad" kategorisi teorisini hiç duymamış insanlara saçma geliyor.
Scott Wisniewski

4
@Scott: Kapsamlı yorumlarım Haskell'i savunmaya başlamış gibi göründüyse özür dilerim. Gizli el sıkışması ile ilgili mizahın tadını çıkarıyorum ve az ya da çok doğru olduğunu söylediğimi fark edeceksin. :-) Abelian Grupları "şişmiş kırışıklık kapsülleri" olarak adlandırdıysanız, monad'lara "daha iyi bir isim" vermeye çalışmakla aynı hatayı yapıyorsunuz (çapraz başvuru F # "hesaplama ifadeleri"): terim var ve hangi monad'ları bilmeyi önemseyen insanlar "sıcak bulanık şeyler" (ya da "hesaplama ifadeleri") değildir. "Tip operatörü" terimini doğru şekilde kullandığınızı anlarsam, monad'lardan başka birçok tip operatörü vardır.
Jared Updike

35

(Ayrıca bkz. Monad nedir? )

Monads için iyi bir motivasyon sigfpe (Dan Piponi) 'nin Monad'ları icat etmiş olabilirsiniz! (Ve Belki Zaten Sahipsinizdir) . Birçoğu yanlış bir şekilde monadları çeşitli benzetmeler kullanarak "basit terimlerle" açıklamaya çalışan diğer monad öğreticilerinin bir sürü vardır : bu monad öğretici yanlıştır ; onlardan kaçının.

DR MacIver'in dediği gibi Bize dilinizin neden berbat olduğunu söyleyin :

Haskell hakkında nefret ettiğim şeyler:

Açık olanla başlayalım. Monad eğiticileri. Hayır, monadlar değil. Özellikle öğreticiler. Onlar sonsuz, aşırı şişmiş ve sevgili tanrı onlar sıkıcı. Dahası, gerçekten yardımcı olduklarına dair ikna edici bir kanıt görmedim. Sınıf tanımını okuyun, biraz kod yazın, korkutucu ismin üstesinden gelin.

Belki de monad'ı anladığını mı söylüyorsun? Güzel, geliyorsun. Sadece diğer monad'ları kullanmaya başlayın ve er ya da geç monadların genel olarak ne olduğunu anlayacaksınız.

[Matematiksel olarak yönlendirildiyseniz, düzinelerce öğreticiyi görmezden gelmek ve tanımı öğrenmek ya da kategori teorisindeki dersleri takip etmek isteyebilirsiniz :) Tanımın ana kısmı, bir Monad M'nin her biri için tanımlayan bir "tip oluşturucu" içermesidir. mevcut "T" tipi yeni bir "MT" tipi ve "normal" tipler ile "M" tipleri arasında gidip gelmenin bazı yolları.]

Ayrıca, şaşırtıcı bir şekilde, monad'lara en iyi girişlerden biri, aslında monadları tanıtan ilk akademik makalelerden biri olan Philip Wadler'in Monads'ı fonksiyonel programlama için . Aslında, yapay öğreticilerin çoğunun aksine , pratik, önemsiz olmayan motive edici örnekleri var.


2
Wadler'in gazetesi ile ilgili tek sorun gösterim farklıdır, ancak makalenin oldukça cazip olduğunu ve monad uygulamak için açık bir özlü motivasyon olduğunu kabul ediyorum.
Jared Updike

"Monad öğretici yanlış" için +1. Monad'larla ilgili öğreticiler, tamsayı sayıları kavramını açıklamaya çalışan birkaç öğreticiye sahip olmaya benzer. Bir öğretici "1 bir elmaya benzer" derdi; başka bir derste "2 bir armut gibidir" der; üçüncüsü "3 temelde bir portakaldır" der. Ancak hiçbir resmi tek bir öğreticiden asla alamazsınız. Bundan aldığım şey, monadların birçok farklı amaç için kullanılabilecek soyut bir kavram olması.
stakx -

@stakx: Evet, doğru. Ama monad'ların öğrenemeyeceğiniz veya öğrenmemeniz gereken bir soyutlama olduğu anlamına gelmedim; ancak altta yatan bir soyutlamayı algılamak için yeterli somut örnekler gördükten sonra öğrenmek en iyisidir. Diğer cevabımı burada gör .
ShreevatsaR

5
Bazen okuyucuyu karmaşık veya yararlı şeyler yapan kod kullanarak monad'ların yararlı olduğuna ikna etmeye çalışan çok fazla öğretici olduğunu hissediyorum. Bu benim aylarca anlayışımı engelledi. Ben bu şekilde öğrenmiyorum. Zihinsel olarak geçebileceğim aptalca bir şey yaparak son derece basit bir kod görmeyi tercih ederim ve bu tür bir örnek bulamadım. İlk örneğin karmaşık bir dilbilgisini ayrıştırmak için bir monad olup olmadığını öğrenemiyorum. Tamsayıları toplamak için bir monad olup olmadığını öğrenebilirim.
Rafael S.Calsaverini

Sadece tip yapıcıdan söz edilmiyor: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Monadlar, soyut veri türlerinin verilere akışını kontrol etmektir.

Başka bir deyişle, birçok geliştirici Kümeler, Listeler, Sözlükler (veya Hashler veya Haritalar) ve Ağaçlar fikrinden memnun. Bu veri türleri içinde birçok özel durum vardır (örneğin InsertionOrderPreservingIdentityHashMap).

Bununla birlikte, program "akışı" ile karşı karşıya kaldığında, birçok geliştirici anahtar / kasa, do, while, goto (grr) ve (belki) kapanışlarından daha fazla yapıya maruz kalmamıştır.

Yani, bir monad basitçe bir kontrol akış yapısıdır. Monad'ın yerini alacak daha iyi bir ifade 'kontrol tipi' olacaktır.

Bu nedenle, bir monad, kontrol mantığı, ifadeler veya işlevler için yuvalara sahiptir - veri yapılarındaki eşdeğer, bazı veri yapılarının veri eklemenize ve kaldırmanıza izin verdiğini söylemek olacaktır.

Örneğin, "if" monad:

if( clause ) then block

en basitinde iki yuva vardır - bir cümle ve bir blok. ifMonad genellikle maddesinin sonucunu değerlendirmek için inşa edilmiş ve sahte değilse, blok değerlendirmek edilir. Birçok geliştirici 'if' öğrendiklerinde monad'larla tanışmaz ve sadece etkili mantık yazmak için monad'ları anlamak gerekli değildir.

Monadlar, veri yapılarının daha karmaşık hale gelmesiyle aynı şekilde daha karmaşık hale gelebilir, ancak benzer anlambilime sahip, ancak farklı uygulamalar ve sözdizimi olan birçok geniş monad kategorisi vardır.

Kuşkusuz, veri yapıları aynı şekilde monadlar üzerinde yinelenebilir veya geçilebilir.

Derleyiciler kullanıcı tanımlı monadları destekleyebilir veya desteklemeyebilir. Haskell kesinlikle biliyor. Ioke bazı benzer yeteneklere sahiptir, ancak monad terimi dilde kullanılmamaktadır.


14

En sevdiğim Monad öğreticim:

http://www.haskell.org/haskellwiki/All_About_Monads

("monad eğitimi" için Google aramada 170.000 sonuçtan!)

@Stu: Monad'ların noktası, aksi takdirde saf koda sıralı anlambilim eklemenize izin vermektir; hatta monadlar oluşturabilir (Monad Transformers kullanarak) ve örneğin hata işleme, paylaşılan durum ve günlük kaydı ile ayrıştırma gibi daha ilginç ve karmaşık birleşik semantikler elde edebilirsiniz. Tüm bunlar saf kodda mümkündür, monadlar sadece onu soyutlamanıza ve modüler kütüphanelerde (programlamada her zaman iyi) tekrar kullanmanıza izin verir, ayrıca zorunlu görünmesini sağlamak için uygun sözdizimi sağlar.

Haskell zaten operatör aşırı yüklemesine sahiptir [1]: Java veya C # 'da arayüzleri kullanabileceği gibi tip sınıflarını kullanır, ancak Haskell sadece + && ve> gibi alfasayısal olmayan simgelerin infix tanımlayıcıları olarak kullanılmasına izin verir. "Noktalı virgülün aşırı yüklenmesi" anlamına gelirse, yalnızca operatöre bakmak için aşırı yükleme yapar [2]. Kara büyü gibi geliyor ve "noktalı virgülün aşırı yüklenmesi" için sorun istiyor (resim girişimci Perl bilgisayar korsanları bu fikrin rüzgârını alıyor), ancak nokta, monadlar olmadan noktalı virgül olmamasıdır, çünkü tamamen işlevsel kod açık sıralamaya gerek duymaz veya buna izin vermez.

Tüm bunlar gerekenden çok daha karmaşık geliyor. sigfpe'nin makalesi oldukça havalı ama Haskell'i açıklamak için kullanıyor, Haskell'i Monad'ları anlamaya ve Monad'ları Haskell'e anlamaya yönelik tavuk ve yumurta sorununu kırmayı başaramıyor.

[1] Bu, monad'lardan ayrı bir konudur, ancak monadlar Haskell'in operatör aşırı yükleme özelliğini kullanır.

[2] Monadik eylemleri zincirleme operatörü >> = ("bağlayıcı" olarak telaffuz edilir) olduğu için, aynı zamanda parantez ve noktalı virgül ve / veya girinti ve yeni satırlar kullanmanıza izin veren sözdizimsel şeker ("do") olduğundan, bu da aşırı bir basitleştirmedir.


9

Son zamanlarda Monad'ları farklı bir şekilde düşünüyordum. Bunları , yeni tür polimorfizmi mümkün kılan, yürütme düzenini matematiksel bir şekilde soyutlama olarak düşünüyorum .

Zorunlu bir dil kullanıyorsanız ve bazı ifadeleri sırayla yazıyorsanız, HER ZAMAN kodu tam olarak bu sırayla çalışır.

Ve basit durumda, bir monad kullandığınızda, aynı hissettirir - sırayla gerçekleşen ifadelerin bir listesini tanımlarsınız. Bunun dışında, hangi monad'ı kullandığınıza bağlı olarak, kodunuz (IO monad'da olduğu gibi) sırayla çalışabilir, aynı anda birkaç öğeye paralel olarak (Liste monad'ında olduğu gibi), yarı yolda durabilir (Belki de monad'da olduğu gibi) , daha sonra devam ettirilmek üzere yarı yolda duraklayabilir (bir Yeniden Başlatma monadında olduğu gibi), baştan başlayıp (bir İşlem monad'ında olduğu gibi) başlayabilir veya diğer seçenekleri (Mantık monadında olduğu gibi) denemek için yarı yolda geri sarabilir. .

Monadlar polimorfik olduğundan, ihtiyaçlarınıza bağlı olarak aynı kodu farklı monadlarda çalıştırmak mümkündür.

Ayrıca, bazı durumlarda, aynı anda birden fazla özellik elde etmek için monadları (monad transformatörleri ile) birleştirmek mümkündür.


9

Ben hala monads için yeniyim, ama okumak için gerçekten iyi hissettiğim bir bağlantı paylaşacağımı düşündüm (RESİMLERLE !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ layman için monads / (ilişki yok)

Temel olarak, makaleden aldığım sıcak ve bulanık konsept, monad'ların temel olarak farklı işlevlerin kompozit bir şekilde çalışmasına izin veren, yani birden fazla işlevi dizebilme ve tutarsız dönüş hakkında endişelenmeden bunları eşleştirebilme ve bağdaştırma kabiliyetleri türleri ve benzeri. Dolayısıyla bu bağdaştırıcıları yapmaya çalışırken BIND işlevi elma ile elma ve portakal ile portakal tutmakla görevlidir. ASANSÖR fonksiyonu ise "düşük seviye" fonksiyonlarını almak ve bunları BIND fonksiyonlarıyla çalışmak ve aynı zamanda kompostlanabilir olmak için "yükseltmek" ten sorumludur.

Umarım doğru anladım ve daha da önemlisi, makalenin monadlar hakkında geçerli bir görüşe sahip olmasını umuyorum. Başka bir şey yoksa, bu makale monadlar hakkında daha fazla bilgi edinme isteğimi uyandırdı.


Python örnekleri anlaşılmasını kolaylaştırdı! Paylaşım için teşekkürler.
Ryan Efendy

8

Yukarıdaki mükemmel yanıtlara ek olarak, konsepti JavaScript kütüphanesi jQuery ile (ve DOM'u manipüle etmek için "yöntem zincirleme" kullanma yöntemini ) ilişkilendirerek monad'ları açıklayan aşağıdaki makaleye (Patrick Thomson tarafından) bir bağlantı sunmama izin verin. : jQuery bir Monad

JQuery belgelerine kendisi muhtemelen daha tanıdık "oluşturucu deseni" hakkında dönem "monad" ama görüşmelerin ifade etmez. Bu, belki farkında olmadan bile uygun bir monadınız olduğu gerçeğini değiştirmez.


JQuery kullanıyorsanız, bu açıklama özellikle yararlı olabilir, özellikle Haskell güçlü değilse
byteclub

10
JQuery kesinlikle bir monad değil. Bağlantılı makale yanlış.
Tony Morris

1
"Empatik" olmak çok inandırıcı değildir. Konuyla ilgili bazı yararlı tartışmalar için bkz. JQuery bir monad mı - Yığın Taşması
nealmcb

1
Ayrıca bkz. Douglas Crackford'un Google Talk Monads ve Gonads ve modad yapmak için Javascript kodu, AJAX kütüphaneleri ve Promises'ın benzer davranışlarını genişletmek: douglascrockford / monad · GitHub
nealmcb 25:13


7

Bir monad, ortak bir bağlamı paylaşan hesaplamaları bir araya getirmenin bir yoludur. Bir boru ağı inşa etmek gibidir. Ağı kurarken, içinden akan veri yoktur. Ancak tüm bitleri 'bind' ve 'return' ile birleştirmeyi bitirdiğimde, böyle bir şey çağırıyorum runMyMonad monad datave veriler borulardan akıyor.


1
Bu, Monad'dan daha çok Uygulamalı gibidir. Monads ile bağlanacak bir sonraki boruyu seçmeden önce borulardan veri almanız gerekir.
Peaker

evet, Uygulayıcıyı tarif ettiniz, Monad'ı değil. Monad, borunun içinde o noktaya ulaşan verilere bağlı olarak bir sonraki boru segmentini yerinde inşa etmektir.
Ness Ness

6

Uygulamada, monad, yan etkilerle ve uyumsuz giriş ve geri dönüş değerleriyle (zincirleme için) ilgilenen fonksiyon kompozisyon operatörünün özel bir uygulamasıdır.



5

Orada öğrenirken bana en iyi yardımcı olan iki şey vardı:

Graham Hutton'un Haskell'de Programlama kitabından Bölüm 8, "İşlevsel Ayrıştırıcılar" . Bu aslında monadlardan hiç bahsetmez, ancak bölüm boyunca çalışabilir ve içindeki her şeyi gerçekten anlayabilirseniz, özellikle bir dizi bağlanma işleminin nasıl değerlendirildiğini, monad'ların iç kısımlarını anlayacaksınız. Bunun birkaç deneme yapmasını bekleyin.

Monads Hakkında Her şey öğretici . Bu, kullanımlarına birkaç iyi örnek veriyor ve Appendex'teki benzetmenin benim için çalıştığını söylemeliyim.


5

Monoid, bir Monoid ve desteklenen bir türde tanımlanan tüm işlemlerin her zaman Monoid içinde desteklenen bir tür döndürmesini sağlayan bir şey gibi görünmektedir. Örneğin, Herhangi bir sayı + Herhangi bir sayı = Bir sayı, hata yok.

Oysa bölünme iki kesri kabul eder ve bir şekilde haskell'de sonsuz olarak bölünmeyi sıfır olarak tanımlayan bir kesirli döndürür (bir şekilde kesirli bir şekilde olur) ...

Her durumda, Monads, operasyon zincirinizin öngörülebilir bir şekilde davrandığından emin olmanın bir yoludur ve Num -> Num olduğunu iddia eden bir işlev, Num-> Num'ün x ile çağrılan başka bir işleviyle oluşturulduğunu iddia eder. söyle, füzeleri ateşle.

Öte yandan, füzeleri ateşleyen bir fonksiyonumuz varsa, füzeleri de ateşleyen diğer fonksiyonlarla oluşturabiliriz, çünkü amacımız açıktır - füzeleri ateşlemek istiyoruz - ama denemeyecektir tuhaf bir nedenden ötürü "Merhaba Dünya" yazdırarak.

Haskell'de ana tip IO () veya IO [()], bu rahatsızlık garip ve tartışmayacağım ama işte böyle olduğunu düşünüyorum:

Eğer ana fikrim varsa, bir eylem zinciri yapmasını istiyorum, programı çalıştırmamın nedeni bir etki yaratmak - genellikle IO olsa da. Böylece, IO'ları yapmak için IO operasyonlarını ana olarak birbirine bağlayabilirim, başka bir şey yok.

Eğer "IO'ya geri dönmeyen" bir şey yapmaya çalışırsam, program zincirin akmadığından şikayet eder, ya da temelde "Bu bizim yapmaya çalıştığımız şeyle bir IO eylemiyle nasıl ilişkilidir?" programcı düşünce trenlerini, sapmadan ve füzeleri ateşlemeyi düşünmeden tutmak için, sıralama için algoritmalar oluştururken - akmaz.

Temel olarak, Monads derleyiciye bir ipucu gibi gözüküyor "hey, burada bir sayı döndüren bu işlevi biliyorsunuz, aslında her zaman işe yaramaz, bazen bir Sayı üretebilir ve bazen Hiçbir şey, sadece bunu saklayın zihin". Bunu bilerek, bir monadik eylemi iddia etmeye çalışırsanız, monadik eylem derleme zamanı istisnası olarak hareket edebilir "hey, bu aslında bir sayı değil, bu bir sayı olabilir, ama bunu kabul edemezsiniz, bir şeyler yapın akışın kabul edilebilir olmasını sağlamak için. " bu da öngörülemeyen program davranışlarını - adil bir şekilde - önler.

Monadların saflık veya kontrol ile ilgili olmadığı, ancak tüm davranışların öngörülebilir ve tanımlandığı veya derlemediği bir kategorinin kimliğini korumakla ilgili olduğu görülmektedir. Bir şey yapmanız beklendiğinde hiçbir şey yapamazsınız ve hiçbir şey (görünür) yapmanız bekleniyorsa bir şey yapamazsınız.

Monads için düşünebileceğim en büyük neden - Git Prosedür / OOP koduna bakın ve programın nereden başladığını veya bitmediğini bilmediğinizi göreceksiniz, tek gördüğünüz çok atlama ve çok fazla matematik , büyü ve füzeler. Bunu koruyamayacaksınız ve eğer yapabiliyorsanız, herhangi bir bölümünü anlayabilmeniz için zihninizi tüm programın etrafına sarmak için çok fazla zaman harcayacaksınız, çünkü bu bağlamdaki modülerlik birbirine bağlı "bölümlere" dayanmaktadır. Burada kod, verimlilik / ilişkiler arası vaat için mümkün olduğunca ilişkili olacak şekilde optimize edilmiştir. Monadlar çok somuttur ve tanımları gereği iyi tanımlanmıştır ve program akışının analiz edilmesi zor olan parçaların analiz edilmesini ve izole edilmesini sağlar - kendileri monad olduğundan. Bir monad " ya da evreni yok etmek ya da zamanı çarpıtmak - BT'nin OLDUĞU OLDUĞU konusunda hiçbir fikrimiz veya garantimiz yok. Bir monad, OLDUĞU GİBİ GARANTİLERDİR. ki bu çok güçlü. ya da evreni yok etmek ya da zamanı çarpıtmak - BT'nin OLDUĞU OLDUĞU konusunda hiçbir fikrimiz veya garantimiz yok. Bir monad, OLDUĞU GİBİ GARANTİLERDİR. ki bu çok güçlü.

"Gerçek dünya" daki her şey, karışıklığı önleyen kesin gözetilebilir yasalara bağlı olduğu için monad gibi görünmektedir. Bu, sınıf oluşturmak için bu nesnenin tüm işlemlerini taklit etmemiz gerektiği anlamına gelmez, bunun yerine "bir kare bir kare", bir kare dışında bir şey, bir dikdörtgen veya bir daire bile ve "bir karenin alanı" diyebiliriz. Mevcut karelerden birinin uzunluğunun kendisi ile çarpılır.Hangi kareniz olursa olsun, 2B alanda bir kare ise, alan kesinlikle uzunluğu kareden başka bir şey olamaz, kanıtlamak neredeyse önemsizdir. dünyamızın olduğu gibi olduğundan emin olmak için iddialarda bulunmaya gerek yok, sadece programlarımızın yoldan çıkmasını önlemek için gerçekliğin etkilerini kullanıyoruz.

Ben yanlış olduğunu hemen hemen garanti ama bu orada birine yardımcı olabilir düşünüyorum, bu yüzden umarım birine yardımcı olur.


5

Scala bağlamında aşağıdakileri en basit tanım olarak bulacaksınız. Temel olarak flatMap (veya bağlama) 'ilişkiseldir' ve bir kimlik vardır.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Örneğin

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

NOT Kesinlikle bir tanımını konuşan fonksiyonel programlamada Monad bir tanımı aynı değildir Kategori Kuramı Monad dönüşleri tanımlanmıştır, mapve flatten. Bazı eşlemeler altında eşdeğer olmalarına rağmen. Bu sunumlar çok iyi: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

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

Sözde kodda şu üç işlevi göz önünde bulundurun:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fformun sıralı bir çiftini alır ve sıralı bir çift <x, messages>döndürür. İlk öğeye dokunulmaz ve "called f. "ikinci öğeye eklenir . İle aynı g.

Bu işlevleri oluşturabilir ve işlevlerin hangi sırada çağrıldığını gösteren bir dize ile birlikte orijinal değerinizi alabilirsiniz:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Önceki günlük bilgilerine kendi günlük mesajlarını eklemekten fve gbundan sorumlu olmanızdan hoşlanmıyorsunuz . (Tartışmanın uğruna hayal yerine dizeleri ekleyerek, fve gçiftin ikinci öğe üzerinde karmaşık mantığı gerçekleştirmelidir Bu tekrarına bir ağrı olacağını karmaşık mantık ikiye -. Ya da daha fazla -. Farklı fonksiyonlar)

Daha basit fonksiyonlar yazmayı tercih edersiniz:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Ancak bunları oluştururken ne olduğuna bakın:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Sorun olmasıdır geçen bir işlev içine bir çift istediğini vermez. Ama ya bir çifti bir işleve besleyebilseydiniz :

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Oku feed(f, m)"yem olarak miçine f". To beslemek bir çift <x, messages>bir işlev içine fetmektir geçmesi x halinde f, elde <y, message>dışına fve dönüş <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

İşlevlerinizle üç şey yaptığınızda ne olduğuna dikkat edin:

İlk olarak: bir değeri sararsanız ve sonuçta ortaya çıkan çifti bir işleve beslerseniz:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Bu aynı geçiş işlevi değeri.

İkincisi: Eğer bir çifti beslerseniz wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Bu çifti değiştirmez.

Üçüncüsü: alan xve g(x)içine giren bir işlev tanımlarsanız f:

h(x) := feed(f, g(x))

ve içine bir çift besleyin:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Bu, çifti gbeslemek ve elde edilen çifti beslemekle aynıdır f.

Bir monadın çoğuna sahipsiniz. Şimdi sadece programınızdaki veri türlerini bilmeniz gerekir.

Ne tür bir değer <x, "called f. ">? Bu, ne tür bir değere bağlı olduğuna bağlıdır x. Türdeyse x, tçiftiniz "çift tve dize" türünde bir değerdir . Bu türü arayın M t.

Mbir tür yapıcısıdır: Mtek başına bir türe başvurmaz, ancak M _boşluğu bir türle doldurduktan sonra bir türe başvurur . An M intbir çift int ve bir dizedir. An M string, bir dize ve bir dize çiftidir. Vb.

Tebrikler, bir monad yarattınız!

Resmi olarak, monad'ınız bir demet <M, feed, wrap>.

Bir monad, aşağıdaki durumlarda bir demettir <M, feed, wrap>:

  • M bir tür kurucusudur.
  • feeda (a alan tve a döndüren bir işlev M u) ve a alır ve an M tdöndürür M u.
  • wrapa alır vve döndürür M v.

t,, uve vaynı olabilecek veya olmayabilecek üç türdür. Bir monad, kendi monadınız için kanıtladığınız üç özelliği karşılar:

  • Besleme bir sarılmış tbir işlev ile aynı geçen kullanıcıya tişlevi.

    resmen: feed(f, wrap(x)) = f(x)

  • M tİçine bir beslemek için wraphiçbir şey yapmaz M t.

    resmen: feed(wrap, m) = m

  • Bir M t(çağırma m) fonksiyonunu

    • tiçine geçerg
    • Bir alır M u(diyoruz n) dang
    • niçine beslenirf

    aynıdır

    • miçine beslemekg
    • n-den almakg
    • niçine beslemekf

    Resmen: feed(h, m) = feed(f, feed(g, m))neredeh(x) := feed(f, g(x))

Tipik feedolarak bind( >>=Haskell'de AKA ) wrapdenir ve denir return.


5

MonadHaskell bağlamında açıklamaya çalışacağım .

Fonksiyonel programlamada fonksiyon kompozisyonu önemlidir. Programımızın küçük, okunması kolay işlevlerden oluşmasını sağlar.

Diyelim ki iki fonksiyonumuz var: g :: Int -> Stringve f :: String -> Bool.

Biz yapabiliriz (f . g) xsadece aynı olan f (g x)yerlerde, xbir olduğunu Intdeğer.

Kompozisyon yaparken / bir fonksiyonun sonucunu diğerine uygularken, türlerin eşleşmesi önemlidir. Yukarıdaki durumda, döndürülen sonucun gtürü tarafından kabul edilen türle aynı olmalıdır f.

Ancak bazen değerler bağlamlardadır ve bu, türlerin sıralanmasını biraz daha az kolaylaştırır. (Bağlamda değerlere sahip olmak çok yararlıdır. Örneğin, Maybe Inttür Intorada bulunmayan IO Stringbir Stringdeğeri , tür bazı yan etkilerin gerçekleştirilmesinin sonucu olarak orada bulunan bir değeri temsil eder .)

Diyelim ki şimdi g1 :: Int -> Maybe Stringve f1 :: String -> Maybe Bool. g1ve f1çok benzer gve fsırasıyla.

Yapamayız (f1 . g1) xveya f1 (g1 x)nerede xbir Intdeğerdir. Tarafından döndürülen sonucun türü beklendiği gibi g1değil f1.

Yazabilir fve operatörle gbirlikte olabiliriz ., ama şimdi yazamayız f1ve g1ile yapamayız .. Sorun, bir bağlamdaki bir değeri, bağlamda olmayan bir değer bekleyen bir işleve doğrudan iletemememizdir.

Oluşturmak için bir operatör tanıtmamız g1ve f1yazabilmemiz iyi olmaz (f1 OPERATOR g1) xmıydı? g1bağlamdaki bir değeri döndürür. Değer bağlam dışına çıkarılacak ve uygulamasına uygulanacaktır f1. Ve evet, böyle bir operatörümüz var. Öyle <=<.

Ayrıca, >>=biraz farklı bir sözdiziminde de bizim için aynı şeyi yapan operatöre sahibiz .

Biz yazın: g1 x >>= f1. g1 xbir Maybe Intdeğerdir. >>=Operatör o almaya yardımcı olur Int"belki-not-orada" bağlam dışına değerini ve uygulamak f1. Sonucu f1a,, Maybe Booltüm sonucu olacaktır >>=işlem.

Ve son olarak, neden Monadfaydalıdır? Çünkü operatörü Monadtanımlayan tip sınıfı, ve operatörünü tanımlayan tip sınıfı ile >>=aynıdır .Eq==/=

Sonuç olarak, Monadtype sınıfı, >>=bir bağlamdaki değerleri (bu monadik değerleri diyoruz) bir bağlamdaki değerleri beklemeyen işlevlere geçirmemize izin veren operatörü tanımlar . Bağlam ele alınacaktır.

Burada hatırlanması gereken bir şey varsa, bu Monadbağlamlarda değerleri içeren fonksiyon kompozisyonuna izin vermesidir .



IOW, Monad genelleştirilmiş fonksiyon çağrısı protokolüdür.
Ness

Bence cevap sizin için en yararlı olanı. Vurgulamak istediğim şey, bahsettiğiniz işlevlerin yalnızca bağlamlardaki değerleri içermediğine değil, aktif olarak değerleri bağlamlara koyduğu gerçeğine bağlı olduğunu söylemeliyim. Böylece, örneğin, f :: ma -> mb işlevi, başka bir işlevle, g :: mb -> m c'yi kolayca oluşturabilir. Ancak monadlar (özellikle bağlanırlar), girdiyi aynı bağlamda yerleştiren, değeri ilk önce bu bağlamın dışına çıkarmaya gerek kalmadan (bilgiyi değerden etkili bir şekilde kaldıracak) sürekli olarak oluşturmamıza izin verir
James

@ James Ben bunun functors vurgu vurgulanması gerektiğini düşünüyorum?
Jonas

@Jonas tahminen açıklamamıştım sanırım. Fonksiyonların bağlamlara değer koyduğunu söylediğimde, tipleri (a -> mb) olduğunu kastediyorum. Bunlar, bir bağlama bir değer eklemek yeni bilgiler eklediğinden çok yararlıdır, ancak değeri (a -> mb) ve bir (b -> mc) zincirlemek genellikle zor olur bağlamında. Bu nedenle, belirli bağlamlara bağlı olarak bu işlevleri mantıklı bir şekilde zincirlemek için kıvrımlı bir süreç kullanmamız gerekir ve monadlar, bağlamdan bağımsız olarak bunu tutarlı bir şekilde yapmamıza izin verir.
James

5

tl; Dr.

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

prolog

$Fonksiyonların uygulama operatörü

forall a b. a -> b

kanonik olarak tanımlanmıştır

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

Haskell ilkel fonksiyon uygulaması açısından f x( infixl 10).

Bileşim .açısından tanımlandığı $şekilde

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

ve denklikleri karşılar forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.çağrışımsaldır, idsağ ve sol kimliğidir.

Kleisli üçlü

Programlamada, bir monad, monad tipi sınıfının bir örneğine sahip bir işlev türü yapıcıdır. Her biri monad soyutlaması hakkında biraz farklı sezgiler taşıyan birkaç eşdeğer tanım ve uygulama varyantı vardır.

Functor, functor tipi sınıfının bir örneğine sahip ftürden bir tür oluşturucusudur * -> *.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Aşağıdaki statik olarak uygulanan tip protokolüne ek olarak, functor tipi sınıfının örnekleri cebirsel functor yasalarına uymalıdır forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Functor hesaplamalarının türü

forall f t. Functor f => f t

Bir hesaplama c roluşur sonuçları r olan bağlamda c .

Tekli monadic işlevleri veya Kleisli oklar türü var

forall m a b. Functor m => a -> m b

Kleisi okları, bir argüman alan ave monadik bir hesaplama döndüren işlevlerdir m b.

Monadlar kanonik olarak Kleisli üçlü olarak tanımlanmıştır forall m. Functor m =>

(m, return, (=<<))

tip sınıfı olarak uygulandı

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

Kleisli kimlik return değeri teşvik eden bir Kleisli oktur tMonadik bağlam içine m. Uzatma veya Kleisli uygulaması bir hesaplama sonucuna =<<Kleisli oku uygular .a -> m bm a

Kleisli bileşimi <=< , uzama açısından şu şekilde tanımlanır:

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< sağ ok uygulamasının sonuçlarına sol oku uygulayarak iki Kleisli oku oluşturur.

Monad tipi sınıfın örnekleri, Kleisli bileşimi açısından en zarif şekilde belirtilen monad yasalarına uymalıdır :forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<çağrışımsaldır, returnsağ ve sol kimliğidir.

Kimlik

Kimlik türü

type Id t = t

türlerde kimlik işlevi

Id :: * -> *

Fonksiyoner olarak yorumlanır,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Kanonik Haskell'de kimlik monad tanımlanır

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

seçenek

Bir seçenek türü

data Maybe t = Nothing | Just t

Maybe tmutlaka sonuç vermeyen thesaplamayı, “başarısız” olabilecek hesaplamayı kodlar . Seçenek monad tanımlanır

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bbir sonuca yalnızca sonuç verirse uygulanır Maybe a.

newtype Nat = Nat Int

Doğal sayılar sıfıra eşit veya sıfırdan büyük tamsayılar olarak kodlanabilir.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Doğal sayılar çıkarma altında kapatılmaz.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

Opsiyon monad, temel bir istisna işleme biçimini kapsar.

(-? 20) <=< toNat :: Int -> Maybe Nat

Liste

Liste türü üzerinde liste türü

data [] t = [] | t : [t]

infixr 5 :

ve katkı maddesi monoid işlemi “append”

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

doğal bir sonuç veren doğrusal olmayan hesaplamayı kodlar .[t]0, 1, ...t

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Uzatma =<<birleştirir ++tüm listeler [b]uygulamalardan kaynaklanan f xbir Kleisli ait ok a -> [b]elemanlarına [a]tek sonuç listesine [b].

Pozitif tamsayı uygun bölenler Let nolmak

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

sonra

forall n.  let { f = f <=< divisors } in f n   =   []

Monad tipi sınıfın tanımlanmasında, uzatma yerine =<<Haskell standardı, bağlama operatörü olan flipini kullanır >>=.

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Basit olması için, bu açıklama tip sınıfı hiyerarşisini kullanır

class              Functor f
class Functor m => Monad m

Haskell'de mevcut standart hiyerarşi

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

çünkü her monad sadece bir functor değil, aynı zamanda her başvuran bir functor ve her monad da bir aplikatör.

Liste monadını kullanarak zorunlu sözde kod

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

kabaca do blokuna çevirir ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

eşdeğer monad anlama ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

ve ifade

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Gösterim ve monad kavrayışları iç içe bağlanmış ifadeler için sözdizimsel şekerdir. Bağlama operatörü, monadik sonuçların yerel ad bağlaması için kullanılır.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

nerede

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

Koruma fonksiyonu tanımlanmıştır

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

burada birim tipi veya “boş demet”

data () = ()

Seçim ve başarısızlığı destekleyen ilave monadlar bir tip sınıfı kullanılarak soyutlanabilir

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

nerede failve <|>bir monoid oluştururforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

ve faililave monadların emici / yok edici sıfır elementidir

_ =<< fail  =  fail

Eğer varsa

guard (even p) >> return p

even pdoğruysa, muhafız yerel sabit fonksiyon üretir [()]ve tanımı ile>>

\ _ -> return p

sonuca uygulanır (). Yanlışsa , koruma, Kleisli okunun uygulanması için hiçbir sonuç vermeyen liste monad'ını fail( []) üretir, >>bu patlanır.

Durum

Kötü bir şekilde, monadlar durum bilgisi olan hesaplamayı kodlamak için kullanılır.

Bir durum bir işlemci bir fonksiyonudur

forall st t. st -> (t, st)

bir durumu değiştirir stve sonuç verir t. Devlet st herhangi bir şey olabilir. Hiçbir şey, bayrak, saymak, dizi, tanıtıcı, makine, dünya.

Durum işlemcilerinin türü genellikle

type State st t = st -> (t, st)

Devlet işlemci tek hücreli kinded olan * -> *funktoru State st. Durum işlemci monadının Kleisli okları işlevlerdir

forall st a b. a -> (State st) b

Standart Haskell'de durum işlemcisi monadının tembel versiyonu tanımlandı

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Durum işlemcisi bir başlangıç ​​durumu sağlanarak çalıştırılır:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

Devlet erişimi, ilkellerle getve durum bilgisi olan monadlara putgöre soyutlama yöntemleriyle sağlanır :

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdevlet tipinin monad'a işlevsel bir bağımlılığını beyan eder ; örneğin a , durum tipinin benzersiz olacağını belirler .stmState tt

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

voidC cinsinden benzer şekilde kullanılan birim tipi ile.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets genellikle kayıt alanı erişimcilerinde kullanılır.

Değişken iş parçacığının durum monad eşdeğeri

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

nerede s0 :: Int, aynı derecede şeffaf, ancak son derece daha zarif ve pratik

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)eşdeğer etkisiState Int () dışında bir tür hesaplamadır .return ()

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

Monad birliktelik yasası, >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

veya

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

İfade odaklı programlamada olduğu gibi (örneğin Rust), bir bloğun son ifadesi verimini temsil eder. Ciltleme operatörü bazen “programlanabilir noktalı virgül” olarak adlandırılır.

Yapısal zorunluluk programlamasının yineleme kontrol yapısı ilkelleri tek tek taklit edilir

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Giriş çıkış

data World

I / O dünya devleti işlemci monad, saf Haskell'in ve gerçek dünyanın fonksiyonel işlevsel ve zorunlu çalışma semantiğinin bir mutabakatıdır. Gerçek katı uygulamanın yakın bir analogu:

type IO t = World -> (t, World)

Etkileşim saf olmayan ilkellerle kolaylaştırılır

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

IOİlkelleri kullanan kodun safsızlığı , tip sistemi tarafından kalıcı olarak protokollendirilir. Saflık harika olduğu için IO, içinde olan şey kalır IO.

unsafePerformIO :: IO t -> t

Ya da, en azından, yapmalı.

Haskell programının tip imzası

main :: IO ()
main = putStrLn "Hello, World!"

genişler

World -> ((), World)

Bir dünyayı dönüştüren bir işlev.

son söz

Nesneleri içeren kategorisi Haskell, hangilerinin morfizmi Haskell tipleri arasındaki işlevlerdir, “hızlı ve gevşek” kategorisidir Hask.

Bir işlev T, bir kategoriden Cbir kategoriye bir eşlemedir D; içindeki Cbir nesnedeki her nesne içinD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

ve her Cbir morfizm içinD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

nerede X, Ynesneler içinde C. HomC(X, Y)olduğu homomorfizmi sınıfı tüm Morfizmlerin X -> Yiçinde C. Funktoru morfizmanın kimlik ve bileşimin, bir “yapı” korumak zorundadır Colarak, D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

Kleisli kategori bir kategorinin Cbir Kleisli Triple verilir

<T, eta, _*>

bir endofunctor

T : C -> C

( f), bir kimlik morfizmi eta( return) ve bir uzantı operatörü *( =<<).

Her Kleisli morfizmi Hask

      f :  X -> T(Y)
      f :: a -> m b

uzantı operatörü tarafından

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

HaskKleisli kategorisinde bir morfizm verildi

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Kleisli kategorisindeki kompozisyon .Tuzatma açısından verilmiştir.

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

ve aksiyom kategorisini karşılar

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

eşdeğerlik dönüşümlerini uygulayarak

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

uzatma açısından kanonik olarak verilmiştir.

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Monads da değil Kleislian uzantısı açısından tanımlanabilir, ancak doğal bir dönüşüm muiçinde denilen programlaması join. Bir monad, mubir kategorinin üçlü değeri C, bir endofunctor olarak tanımlanır

     T :  C -> C
     f :: * -> *

ve iki doğal dönüşüm

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

denklikleri sağlamak

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

Daha sonra monad tipi sınıfı tanımlanır

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

muSeçenek monadının kanonik uygulaması:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

concatfonksiyon

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

olduğu joinliste monadın.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Uygulamaları join, eşdeğerlik kullanılarak uzatma formundan çevrilebilir

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

muEklenti formuna tersten çeviri şu şekilde yapılır:

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Ama neden bu kadar soyut bir teori programlama için herhangi bir işe yarıyor?

Cevap basit: bilgisayar bilimcileri olarak soyutlamaya değer veriyoruz ! Arayüzü bir yazılım bileşenine göre tasarladığımızda , uygulama hakkında olabildiğince az açığa çıkmasını istiyoruz . Uygulamayı birçok alternatifle, aynı 'kavramın' diğer birçok örneğiyle değiştirebilmek istiyoruz. Birçok program kütüphanesine genel bir arayüz tasarladığımızda, seçtiğimiz arayüzün çeşitli uygulamalara sahip olması daha da önemlidir. Çok yüksek değer verdiğimiz monad kavramının genelliğidir, çünkü kategori teorisi o kadar soyuttur ki kavramları programlama için çok faydalıdır.

O halde aşağıda sunduğumuz monadların genelleştirilmesinin kategori teorisiyle de yakın bir bağlantısı olması şaşırtıcı değildir. Ancak amacımızın çok pratik olduğunu vurguluyoruz: 'kategori teorisi uygulamak' değil, birleştirici kütüphanelerini yapılandırmanın daha genel bir yolunu bulmaktır. Matematikçiler bizim için zaten çok fazla iş yapmışlar bizim şansımız!

dan Arrows için generalising monad'ların John Hughes tarafından


4

Dünyanın ihtiyacı olan başka bir monad blog yazısı, ancak bunun vahşi doğada var olan monadları tanımlamakta yararlı olduğunu düşünüyorum.

Sierpinski üçgeni

Yukarıdaki, çizmeyi hatırlayabildiğim tek fraktal olan Sierpinski üçgeni olarak adlandırılan bir fraktal. Fraktallar, parçaların bütüne benzer olduğu yukarıdaki üçgen gibi kendine benzer bir yapıdır (bu durumda, ana üçgen olarak ölçeğin tam yarısı).

Monadlar fraktallardır. Monadik bir veri yapısı göz önüne alındığında, değerleri veri yapısının başka bir değerini oluşturmak üzere oluşturulabilir. Bu yüzden programlama için yararlıdır ve bu yüzden birçok durumda ortaya çıkar.


3
Eğer "dünya neyi ifade ediyor gelmez ... gerek"? Güzel benzetme olsa!
groverboy

@ icc97 haklısın - anlamı yeterince açık. Alaycılık istemeden, yazardan özür dileriz.
groverboy

Dünyanın ihtiyaç duyduğu şey, bir alaycılığı onaylayan başka bir yorum dizisidir, ancak dikkatlice okuduysam yazdım ama bu açıklığa kavuşturmalıdır.
Eugene Yokota


4

Aşağıdaki " {| a |m}" ifadesinin bir parça monadik veriyi temsil etmesine izin verin . Aşağıdakileri tanıtan bir veri türü a:

        (I got an a!)
          /        
    {| a |m}

İşlev,, fbir monadın nasıl oluşturulacağını bilir, eğer sadece bir tane varsa a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Burada fonksiyonu görüyoruz, fbir monad'ı değerlendirmeye çalışıyor ancak azarlanıyor.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Fonksiyon, kullanarak fçıkarmak için bir yol bulur .a>>=

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Çok az şey fbiliyor, monad ve >>=gizli anlaşma içinde.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Ama aslında ne hakkında konuşuyorlar? Bu, monad'a bağlı. Sadece soyutta konuşmanın kullanımı sınırlıdır; anlayışın üstesinden gelmek için belirli monadlarla biraz deneyim sahibi olmanız gerekir.

Örneğin, veri türü Belki

 data Maybe a = Nothing | Just a

aşağıdaki gibi davranacak bir monad örneğine sahiptir ...

Nerede, dava ise Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Ancak Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Belki de Monad, aslında bir areklamı içeriyorsa, bir hesaplamanın devam etmesine izin verir , ancak yapmazsa hesaplamayı iptal eder. Bununla birlikte sonuç, çıktısı olmasa da hala bir monadik veri parçasıdır f. Bu nedenle, belki de monad başarısızlık bağlamını temsil etmek için kullanılır.

Farklı monadlar farklı davranırlar. Listeler, monadik örneklere sahip diğer veri türleridir. Aşağıdaki gibi davranırlar:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

Bu durumda, işlev girişinden bir listenin nasıl oluşturulacağını biliyordu, ancak ekstra giriş ve ekstra listelerle ne yapılacağını bilmiyordu. Bağlama >>=yardımcı fbirden fazla çıkış kombine edilmesi sureti ile. Bu örneği, >>=ayıklamadan sorumlu aolsa da, aynı zamanda nihai sınır çıktısına da erişimi olduğunu göstermek için ekliyorum f. Gerçekten de, anihai çıktının aynı türde içeriğe sahip olduğunu bilmediği sürece hiçbir zaman ayıklanmayacaktır .

Farklı bağlamları temsil etmek için kullanılan başka monadlar da vardır. İşte birkaç tanesinin bazı karakteristikleri. IOMonad aslında bir yok a, ama bir adam bilir ve bu alacak asenin için. State stMonad, gizli bir yer alır sto ileteceği frağmen masanın altında fsadece soran geldi a. Reader rMonad benzer State stsadece sağlayan rağmen, fbakmak r.

Bütün mesele şu ki, kendisini bir Monad olarak ilan eden herhangi bir veri türü, monad'dan bir değer çıkarılması konusunda bir tür bağlam beyan ediyor. Tüm bunlardan büyük kazanç mı? Bir çeşit bağlamda bir hesaplama yapmak yeterince kolay. Bununla birlikte, birden çok bağlam yüklü hesaplamayı birleştirirken dağınık olabilir. Monad operasyonları, programlayıcının yapmasına gerek kalmaması için bağlam etkileşimlerinin çözümlenmesine özen gösterir.

>>=Bu özerkliğin bir kısmını uzaklaştırarak karmaşayı hafiflettiğini unutmayın f. Yani, Nothingörneğin, yukarıdaki durumda, fartık ne yapacağına karar vermez Nothing; kodlanmış >>=. Bu değiş tokuş. İçin gerekli idiyse fkarar vermek durumunda ne yapılacağını Nothing, sonra fbir fonksiyon olması gerekirdi Maybe aiçin Maybe b. Bu durumda, Maybebir monad olmak önemsizdir.

Bununla birlikte, bazen bir veri türünün yapıcılarını dışa aktarmadığını (IO'ya bakarken) unutmayın ve reklamı yapılan değerle çalışmak istiyorsak, monadic arayüzü ile çalışmaktan başka seçeneğimiz yoktur.


3

Monad, durumu değişen nesneleri kapsüllemek için kullanılan bir şeydir. En sık, başka türlü değiştirilebilir duruma (ör. Haskell) sahip olmanıza izin vermeyen dillerde karşılaşılır.

Dosya G / Ç için bir örnek verilebilir.

Değişen durum niteliğini yalnızca Monad'ı kullanan koda yalıtmak için I / O dosyası için bir monad kullanabilirsiniz. Monad'ın içindeki kod, Monad dışındaki dünyanın değişen durumunu etkili bir şekilde göz ardı edebilir - bu, programınızın genel etkisi hakkında mantık yürütmeyi çok daha kolay hale getirir.


3
Anladığım kadarıyla, monadlar bundan daha fazlası. Değişken durumu "saf" fonksiyonel dillerin içine almak monadların sadece bir uygulamasıdır.
thSoft
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.