Neden monadlara ihtiyacımız var?


366

Benim düşünceme göre, ünlü "Monad nedir?" özellikle de en çok oylananlar, monad'ların neden gerçekten gerekli olduğunu açıkça açıklamadan bir monadın ne olduğunu açıklamaya çalışın . Bir sorunun çözümü olarak açıklanabilirler mi?




4
Daha önce hangi araştırmayı yaptınız? Nereye baktın? Hangi kaynakları buldunuz? Sizden sormadan önce önemli miktarda araştırma yapmanızı ve bize hangi araştırmayı yaptığınızı sormanızı bekliyoruz . Kaynakların motivasyonunu açıklamaya çalışan birçok kaynak var - eğer hiç bulamadıysanız, biraz daha araştırma yapmanız gerekebilir. Biraz bulduysanız ancak size yardımcı olmadıysanız, ne bulduğunuzu ve neden sizin için özel olarak çalışmadığını açıklamanız daha iyi bir soru olurdu.
DW

8
Bu kesinlikle Programmers.StackExchange için daha iyi bir seçimdir ve StackOverflow için iyi bir uyum değildir. Mümkünse göç etmek için oy verirdim, ama yapamam. = (
jpmc26

3
@ jpmc26 Büyük olasılıkla orada "öncelikle görüş tabanlı" olarak kapanacaktı; burada en azından bir şans var (çok sayıda upvotes tarafından gösterildiği gibi, dün hızlı yeniden açıldı ve henüz yakın oy yok)
Izkata

Yanıtlar:


580

Neden monadlara ihtiyacımız var?

  1. Programlamak istiyoruz Sadece fonksiyonları kullanarak . (sonuçta "işlevsel 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)). Bu şekilde, "program" da bir işlevdir:main = 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). Sahibiz hayır "istisnalar" FP (bir istisna bir işlev değil). Bunu nasıl çözeriz?

    Çözüm: Fonksiyonların iki tür şey döndürmesine izin verelim : g : Real,Real -> Real(iki realiteden gerçeğe fonksiyon) yerine izin verelimg : 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. Bağlanabileceğimiz her işlevi değiştirmek istemiyoruzg a tüketmek içinMaybe Real .

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

    Bizim durumumuzda: g >>= f(connect / oluşturma giçin f). Biz istiyoruz >>=almakg , 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). Ayrıca, "boks tipi" (farklı kutu, farklı uyarlama algoritması) başına yalnızca bir kez>>= yazılması gerektiğini unutmayın .

  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ı olacak bir besteciye / bağlayıcıya 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

Tamamen mutluluk!


64
@Carl Lütfen bizi aydınlatmak için daha iyi bir cevap yazın
XrXr

15
@Carl Cevapta, bu modelden (nokta 6) yararlanan birçok sorunun olduğu ve IOmonad'ın listede sadece bir sorun daha IO(nokta 7) olduğu açıktır . Öte yandan, IOsadece bir kez ve sonunda görünür, bu nedenle, "zamanının çoğunu ... IO hakkında" anlamayın.
cibercitizen1

4
Monadlarla ilgili büyük yanılgılar: devletle ilgili monadlar; istisna yönetimi ile ilgili monad'lar; monadlar olmadan saf FPL'de IO uygulamanın hiçbir yolu yoktur; monadlar açıktır (çelişkilidir Either). En çok cevap "Neden functorlara ihtiyacımız var?"
vlastachu

4
"6. 2. Çıktının girdisine g >>= fbağlanmasına yardımcı olacak bir besteciye / bağlayıcıya sahip olun , bu yüzden hiç değiştirmek zorunda değiliz ." gff bu hiç doğru değil . Önce, içinde f(g(x,y)), fher şeyi üretebilir. Olabilir f:: Real -> String. "Monadik kompozisyon" ile üretmek için değiştirilmelidirMaybe String , aksi takdirde tipler uymaz. Dahası, >>=kendisi uymuyor !! O var >=>, bu oluşacağının değil >>=. Carl'ın cevabı altında dfeuer ile tartışmaya bakın.
Ness

3
Cevabınız, IMO'nun gerçekten de en iyi şekilde "işlevlerin" (Kleisli okları) kompozisyonu / müttefiki hakkında olduğu, ancak ne türün nereye gittiğinin kesin ayrıntıları, onları "monad" yapan şeydir. kutuları her türlü şekilde (Functor vb.) bağlayabilirsiniz. Onları birbirine bağlamanın bu özel yolu "monad" ı tanımlayan şeydir.
Ness

219

Cevap, elbette, "Yapmıyoruz" . Tüm soyutlamalarda olduğu gibi, gerekli değildir.

Haskell'in monad soyutlamasına ihtiyacı yoktur. ES'yi saf bir dilde yapmak gerekli değildir. Bu IOtip kendi başına iyi halleder. Mevcut Monadik desugaring dobloklarına desugaring ile ikame edilebilir bindIO, returnIOve failIOtarif edildiği gibiGHC.Base modül. (Hack üzerinde belgelenmiş bir modül değil, bu yüzden kaynağına işaret etmem gerekecek belgeleme için .) Hayır, monad soyutlamasına gerek yok.

Peki gerekli değilse, neden var? Çünkü birçok hesaplama örüntüsünün monadik yapılar oluşturduğu bulunmuştur. Bir yapının soyutlanması, o yapının tüm örneklerinde çalışan kod yazılmasına izin verir. Daha kısaca söylemek gerekirse - yeniden kod kullanın.

İşlevsel dillerde, kodun yeniden kullanımı için bulunan en güçlü araç, işlevlerin bileşimi olmuştur. İyi yaşlı(.) :: (b -> c) -> (a -> b) -> (a -> c) operatör son derece güçlüdür. Minik fonksiyonları yazmayı ve bunları minimal sözdizimsel veya anlamsal ek yük ile yapıştırmayı kolaylaştırır.

Ancak türlerin tam olarak işe yaramadığı durumlar vardır. Sahip olduğunuzda ne yaparsınız foo :: (b -> Maybe c)ve bar :: (a -> Maybe b)? foo . bardaktilo kontrol etmez, çünkü bveMaybe b aynı tip değildir.

Ama ... neredeyse doğru. Sadece biraz boşluk istiyorsun. Temelmiş Maybe bgibi davranabilmek istiyorsun b. Yine de, onlara aynı tipte davranmak kötü bir fikirdir. Bu, Tony Hoare'nin ünlü olarak milyar dolarlık hata olarak adlandırdığı boş işaretçilerle aynı şeydir . Bu yüzden aynı tip olarak tedavi edemezseniz, belki de kompozisyon mekanizmasını genişletmenin bir yolunu bulabilirsiniz (.).

Bu durumda, temeldeki teoriyi gerçekten incelemek önemlidir (.). Neyse ki, birisi bunu zaten bizim için yaptı. Bir kategori olarak bilinen bir matematiksel yapının birleşimi (.)ve idoluşturulması ortaya çıkıyor . Ancak kategori oluşturmanın başka yolları da var. Örneğin, bir Kleisli kategorisi, oluşturulan nesnelerin biraz artırılmasına izin verir. A Kleisli kategorisi oluşacak ve . Yani, kategorideki nesneler a ile artar , böylece olurMaybe(.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)id :: a -> Maybe a(->)Maybe(a -> b)(a -> Maybe b) .

Ve aniden, kompozisyonun gücünü geleneksel (.)operasyonun çalışmadığı şeylere genişlettik . Bu yeni bir soyutlama gücü kaynağıdır. Kleisli kategorileri sadece daha fazla türle çalışır Maybe. Kategori yasalarına uyarak, uygun bir kategori oluşturabilen her türle çalışırlar.

  1. Sol kimlik: id . f =f
  2. Doğru kimlik: f . id =f
  3. Birliktelik: f . (g . h)=(f . g) . h

Türünüzün bu üç yasaya uyduğunu kanıtlayabildiğiniz sürece, onu bir Kleisli kategorisine dönüştürebilirsiniz. Ve bununla ilgili en önemli şey nedir? Monad'ların Kleisli kategorileriyle tamamen aynı olduğu ortaya çıkıyor. Monad'lerreturn Kleisli aynıdır id. Monad'nin (>>=)Kleisli ile aynı değildir (.), ancak diğer açısından çok kolay yazmak için her çıkıyor. Ve kategori yasaları arasındaki fark üzerinden onları çevirmek monad yasaları, aynıdır (>>=)ve (.).

Öyleyse neden tüm bu rahatsızlıktan geçiyorsun? Neden Monaddilde bir soyutlama var ? Yukarıda bahsettiğim gibi, kodun yeniden kullanılmasını sağlıyor. Hatta kodun iki farklı boyutta yeniden kullanılmasını sağlar.

Kod yeniden kullanımının ilk boyutu doğrudan soyutlamanın varlığından gelir. Soyutlamanın tüm örneklerinde çalışan bir kod yazabilirsiniz. Herhangi bir örneğiyle çalışan döngülerden oluşan monad-loop paketinin tamamı var Monad.

İkinci boyut dolaylıdır, ancak kompozisyonun varlığından kaynaklanır. Kompozisyon kolay olduğunda, küçük, yeniden kullanılabilir parçalara kod yazmak doğaldır. Bu, (.)işlevler için operatörün küçük, yeniden kullanılabilir işlevler yazmasını teşvik etmesiyle aynıdır .

Öyleyse soyutlama neden var? Kodda daha fazla kompozisyon sağlayan, yeniden kullanılabilir kod oluşturmaya ve daha fazla yeniden kullanılabilir kod oluşturulmasını teşvik eden bir araç olduğu kanıtlanmıştır. Kodun yeniden kullanımı, programlamanın kutsal yerlerinden biridir. Monad soyutlaması var çünkü bizi biraz bu kutsal kâse doğru hareket ettiriyor.


2
Kategoriler ile Kleisli kategorileri arasındaki ilişkiyi açıklayabilir misiniz? Tanımladığınız üç yasa herhangi bir kategoride geçerlidir.
dfeuer

1
@dfeuer Oh. Kod koymak için newtype Kleisli m a b = Kleisli (a -> m b). Kleisli kategorileri, kategorik döndürme türünün ( bbu durumda) bir tür yapıcısına argüman olduğu işlevlerdir m. Iff Kleisli mbir kategori oluşturur m, bir Monad'dır.
Carl

1
Kategorik getiri türü tam olarak nedir? Kleisli meşyasını gelen oklar bu Haskell tip ve bir kategori oluşturur görünmektedir aiçin bişlevleri vardır aiçin m bolan, id = returnve (.) = (<=<). Bu doğru mu, yoksa farklı seviyelerde şeyleri mi karıştırıyorum?
dfeuer

1
@dfeuer Doğru. Nesneler tüm türlerdir ve morfizmler türler arasındadır ave bbasit işlevler değildir. mFonksiyonun dönüş değerinde ekstra ile dekore edilmiştir .
Carl

1
Kategori Teorisi terminolojisine gerçekten ihtiyaç var mı? Belki, türlerin resimlerin nasıl çizileceğine dair tipin DNA olacağı resimlere dönüştürdüğünüzde Haskell daha kolay olurdu (* olsa da bağımlı tipler) ve daha sonra isimleri küçük yakut karakterler olacak şekilde programınızı yazmak için kullanırsınız. simgesinin üzerinde.
aoeu256

24

Dedi Benjamin Pierce TAPL

Bir tür sistemi, bir programdaki terimlerin çalışma zamanı davranışlarına bir tür statik yaklaşımın hesaplanması olarak kabul edilebilir.

Bu nedenle, güçlü bir yazım sistemi ile donatılmış bir dil, zayıf yazılan bir dilden kesinlikle daha ifade edicidir. Monadları aynı şekilde düşünebilirsiniz.

@Carl ve sigfpe noktası olarak, monadlara, tip sınıflara veya diğer soyut şeylere başvurmadan bir veri türünü istediğiniz tüm işlemlerle donatabilirsiniz. Bununla birlikte, monadlar sadece yeniden kullanılabilir kod yazmanıza değil, aynı zamanda tüm gereksiz detayları soyutlamanıza izin verir.

Örnek olarak, bir listeyi filtrelemek istediğimizi varsayalım. En basit yol, eşit olan filterfonksiyonu kullanmaktır .filter (> 3) [1..10][4,5,6,7,8,9,10]

Bir filterakümülatörü soldan sağa geçiren biraz daha karmaşık bir versiyonu

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

Hepsini almak için iöyle i <= 10, sum [1..i] > 4, sum [1..i] < 25yazabiliriz ki

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

eşittir [3,4,5,6].

Veya nubyinelenen öğeleri bir listeden kaldıran işlevi şu şekilde yeniden tanımlayabiliriz filterAccum:

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]eşittir [1,2,4,5,3,8,9]. Burada bir liste akümülatör olarak geçmektedir. Kod çalışır, çünkü liste monadından ayrılmak mümkündür, bu nedenle tüm hesaplama saf kalır ( aslında notElemkullanmaz >>=, ancak kullanabilir). Bununla birlikte, IO monadından güvenli bir şekilde ayrılmak mümkün değildir (yani, bir IO eylemini gerçekleştiremez ve saf bir değer döndüremezsiniz - değer her zaman IO monadına sarılacaktır). Başka bir örnek değiştirilebilir dizilerdir: Değişken bir dizinin yaşadığı ST monadından çıktıktan sonra diziyi artık sabit zamanda güncelleyemezsiniz. Bu yüzden Control.Monadmodülden monadik bir filtreye ihtiyacımız var :

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterMbir listedeki tüm elemanlar için monadik bir eylem yürütür ve monadik eylemin geri döndüğü öğeler verir True.

Dizili bir filtreleme örneği:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

[1,2,4,5,3,8,9]beklendiği gibi yazdırır .

Ve hangi elemanların geri döneceğini soran IO monad'lı bir sürüm:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

Örneğin

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

Ve son bir örnek olarak, filterAccumşu terimlerle tanımlanabilir filterM:

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

StateTsadece sıradan bir veri türü olan kaputun altında kullanılan monad ile .

Bu örnek, monad'ların yalnızca hesaplamalı bağlamı soyutlamanıza ve (@Carl'ın açıkladığı gibi monad'ların birleştirilebilirliğine bağlı olarak) temiz yeniden kullanılabilir kod yazmanıza izin vermekle kalmayıp, aynı zamanda kullanıcı tanımlı veri türlerini ve yerleşik ilkelleri aynı şekilde ele almanızı sağlar.


1
Bu cevap neden Monad tip sınıfına ihtiyacımız olduğunu açıklıyor. Neden başka bir şeye değil de monadlara ihtiyacımız olduğunu anlamanın en iyi yolu, monadlar ve uygulanabilir functorlar arasındaki farkı okumaktır: bir , iki .
user3237465

20

IOÖzellikle olağanüstü bir monad olarak görülmesi gerektiğini düşünmüyorum , ancak yeni başlayanlar için kesinlikle daha şaşırtıcı olanlardan biri, bu yüzden açıklamam için kullanacağım.

Haskell için saf bir IO sistemi kurmak

Tamamen işlevsel bir dil (ve aslında Haskell'in başladığı) için akla gelebilecek en basit IO sistemi şudur:

main :: String -> String
main _ = "Hello World"

Tembellik ile, bu basit imza aslında etkileşimli terminal programları oluşturmak için yeterlidir - yine de çok sınırlı. En sinir bozucu, sadece metin çıktısı alabilmemiz. Daha heyecan verici çıktı olanakları eklersek ne olur?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

sevimli, ama elbette çok daha gerçekçi bir “alternatif çıktı” bir dosyaya yazıyor olurdu . Ama sonra dosyalardan okumak için bir yol da istersiniz . Herhangi bir şans?

main₁Programımızı aldığımızda ve sadece bir dosyayı işleme koyduğumuzda (işletim sistemi olanaklarını kullanarak), esasen dosya okuma uyguladık. Haskell dilinden bu dosya okumayı tetikleyebilseydik ...

readFile :: Filepath -> (String -> [Output]) -> [Output]

Bu bir “etkileşimli program” kullanır String->[Output] , bir dosyadan elde edilen bir dizeyi besler ve verilen programı yürüten etkileşimli olmayan bir program verir.

Burada bir sorun var: dosyanın ne zaman okunduğuna dair bir fikrimiz yok . [Output]Liste emin için güzel bir emir verir çıkışları , ama biz ne zaman için sipariş alamadım girişler yapılacaktır.

Çözüm: Girdi olaylarını, yapılacaklar listesindeki öğeleri de yapın.

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

Tamam, şimdi bir dengesizliği tespit edebilirsiniz: bir dosyayı okuyabilir ve çıktıyı ona bağımlı hale getirebilirsiniz, ancak örneğin başka bir dosyayı okumaya karar vermek için dosya içeriğini kullanamazsınız. Açık çözüm: Girdi olaylarının sonucunu da IOsadece bir tür yapmak Output. Bu emin basit metin çıkışı içerir, ama aynı zamanda ek dosyaları okuma sağlar vb ..

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

Bu, aslında bir programda isteyebileceğiniz herhangi bir dosya işlemini ifade etmenize izin verecektir (belki de iyi performansla olmasa da), ancak biraz fazla karmaşıktır:

  • main₃eylemlerin bir listesini verir . Neden :: IO₁özel bir durum olan imzayı kullanmıyoruz ?

  • Listeler artık program akışına gerçekten güvenilir bir genel bakış sunmuyor: sonraki hesaplamaların çoğu yalnızca bazı giriş işlemlerinin sonucu olarak “duyurulacak”. Bu yüzden liste yapısını silebiliriz ve her çıkış işlemi için bir "ve sonra" yaparız.

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

Çok kötü değil!

Peki tüm bunların monadlarla ne ilgisi var?

Uygulamada, tüm programlarınızı tanımlamak için düz yapıcılar kullanmak istemezsiniz. Bu tür temel kurucuların iyi bir çiftine ihtiyaç duyulacaktı, ancak çoğu üst düzey şey için bazı güzel üst düzey imzalı bir işlev yazmak istiyoruz. Bunların çoğunun oldukça benzer görüneceği ortaya çıktı: bir tür anlamlı tip değeri kabul edin ve sonuç olarak bir IO eylemi verin.

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

Açıkçası burada bir örüntü var ve bunu şöyle yazsak iyi olur

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

Şimdi bu tanıdık gelmeye başlıyor, ancak hala sadece kaputun altındaki ince kılık değiştirmiş düz işlevlerle uğraşıyoruz ve bu riskli: her bir “değer-eylemi”, içerilen herhangi bir işlevin (başka bir eylemin sonuç eylemini) yürütme sorumluluğuna sahiptir (başka tüm programın kontrol akışı, ortada bir kötü davranışla kolayca bozulabilir). Bu gereksinimi açıkça ortaya koysak iyi olur. Bunların monad yasaları olduğu ortaya çıkıyor , ancak standart bağlama / birleştirme operatörleri olmadan onları gerçekten formüle edebileceğimizden emin değilim.

Her halükarda, uygun bir monad örneğine sahip bir IO formülasyonuna ulaştık:

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

Açıkçası bu, ES'nin etkin bir uygulaması değildir, ancak prensipte kullanılabilir.


@jdlugosz: IO3 a ≡ Cont IO2 a. Ama bu yorumu daha çok başlangıç ​​monadını bilenlere bir nod olarak anlatmak istedim, çünkü yeni başlayanlar için tam bir üne sahip değil.
leftaroundabout

4

Monadlar , tekrarlayan problemlerin bir sınıfını çözmek için uygun bir çerçevedir. İlk olarak, monadlar functor olmalıdır (yani, öğelere (veya türlerine) bakmadan eşlemeyi desteklemelidir), ayrıca bir bağlama (veya zincirleme) işlemi ve bir element türünden ( return) monadik bir değer yaratmanın bir yolunu da getirmelidirler . Son olarak, bindve returnaynı zamanda monad yasaları olarak adlandırılan, iki denklem (sol ve sağ kimlikler) yerine getirmelidir. (Alternatif olarak, monadları flattening operationbağlama yerine sahip olacak şekilde tanımlanabilir .)

Liste monad yaygın olmayan determinizm ile başa çıkmak için kullanılır. Ciltleme işlemi listenin bir öğesini seçer (sezgisel olarak hepsi paralel dünyalarda ), programcının onlarla bazı hesaplamalar yapmasını sağlar ve sonra tüm dünyalardaki sonuçları tek bir listede birleştirir (iç içe bir listeyi birleştirerek veya düzleştirerek) ). Haskell'in monadik çerçevesinde bir permütasyon fonksiyonunu şöyle tanımlayabiliriz:

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

İşte örnek bir repl oturumu:

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

Liste monadının hiçbir şekilde hesaplamayı etkileyen bir yan olmadığı unutulmamalıdır. Bir monad olan matematiksel bir yapı (yani yukarıda belirtilen arayüzlere ve yasalara uygunluk) yan etkiler anlamına gelmez, ancak yan etki fenomenleri genellikle monadik çerçeveye güzelce uyar.


3

Monadlar temel olarak bir zincirde işlevleri bir araya getirmeye yarar. Dönemi.

Şimdi oluşturma şekilleri mevcut monadlar arasında farklılık gösterir, böylece farklı davranışlara yol açar (örneğin, eyalet monadındaki değişebilir durumu simüle etmek için).

Monadlar hakkındaki karışıklık, çok genel olmak, yani fonksiyonları oluşturmak için bir mekanizma olması, pek çok şey için kullanılabilmeleri, böylece insanları sadece “oluşturma fonksiyonları” olduğunda monadların devlet, IO, vb. Hakkında olduğuna inanmalarına yol açmasıdır. ".

Şimdi, monadlar hakkında ilginç bir şey, kompozisyonun sonucunun her zaman "M a" tipi, yani "M" ile etiketlenmiş bir zarfın içindeki bir değer olmasıdır. Bu özellik, örneğin saf olmayan koddan saf arasındaki açık bir ayrımı uygulamak için gerçekten güzel olur: Tüm saf olmayan eylemleri "IO a" tipindeki işlevler olarak ilan edin ve IO monadını tanımlarken, " "IO a" içindeki bir değer. Sonuç olarak, hiçbir işlev saf olamaz ve aynı zamanda bir "IO a" dan bir değer çıkarır, çünkü saf kalırken böyle bir değer almanın bir yolu yoktur (işlev, kullanılacak "IO" monadının içinde olmalıdır böyle bir değer). (NOT: hiçbir şey mükemmel değildir, bu nedenle "IO deli gömleği" "güvensizPerformio: IO a -> a" kullanılarak kırılabilir.


2

Bir tür oluşturucunuz ve bu tür ailesinin değerlerini döndüren işlevleriniz varsa monad'lara ihtiyacınız vardır . Sonunda, bu tür işlevleri bir araya getirmek istersiniz . Bunlar nedenini cevaplamak için üç temel unsurdur .

Açıklayayım. You have Int, Stringve Realve tip fonksiyonları Int -> String, String -> Realve bu kadar. Bu işlevleri,Int -> Real . Hayat güzel.

Sonra bir gün, yeni bir tür ailesi oluşturmanız gerekir . Bunun nedeni, değer ( Maybe) döndürmeme, hata ( Either), birden çok sonuç (List ) vb. .

MaybeBir tür yapıcı olduğuna dikkat edin . Gibi bir tür alır Intve yeni bir tür döndürür Maybe Int. Hatırlanacak ilk şey, tip oluşturucu yok, monad yok.

Tabii ki, kodunuzda tip oluşturucunuzu kullanmak istiyorsunuz ve yakında Int -> Maybe StringveString -> Maybe Float . Artık işlevlerinizi kolayca birleştiremezsiniz. Hayat artık iyi değil.

Ve işte monadlar kurtarmaya geldiğinde. Bu tür işlevleri tekrar birleştirmenizi sağlar. Sadece kompozisyonu değiştirmeniz gerekiyor . için = == .


2
Bunun tip ailelerle bir ilgisi yok. Aslında neden bahsediyorsun?
dfeuer
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.