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?
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?
Yanıtlar:
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 g
ve sonra isterseniz f
sadece yazın f(g(x,y))
. Bu şekilde, "program" da bir işlevdir:main = f(g(x,y))
. Tamam ama ...
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)).
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 ...
Şimdi ne olacak f(g(x,y))
? f
tü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 g
için f
). Biz istiyoruz >>=
almakg
, incelemek ve Nothing
sadece arama f
ve geri dönmemesi durumunda Nothing
; veya tam tersine, kutuyu çıkarın Real
ve f
onunla besleyin . (Bu algoritma sadece uygulamasıdır >>=
için Maybe
tip). Ayrıca, "boks tipi" (farklı kutu, farklı uyarlama algoritması) başına yalnızca bir kez>>=
yazılması gerektiğini unutmayın .
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 g
bu "kutulu değerleri" döndüren işlevlere sahip olun . 2. Çıktının girdisine g >>= f
bağlanmasına yardımcı olacak bir besteciye / bağlayıcıya sahip olun , bu yüzden hiç değiştirmek zorunda değiliz .g
f
f
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!
IO
monad'ın listede sadece bir sorun daha IO
(nokta 7) olduğu açıktır . Öte yandan, IO
sadece bir kez ve sonunda görünür, bu nedenle, "zamanının çoğunu ... IO hakkında" anlamayın.
Either
). En çok cevap "Neden functorlara ihtiyacımız var?"
g >>= f
bağlanmasına yardımcı olacak bir besteciye / bağlayıcıya sahip olun , bu yüzden hiç değiştirmek zorunda değiliz ." g
f
f
bu hiç doğru değil . Önce, içinde f(g(x,y))
, f
her ş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.
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 IO
tip kendi başına iyi halleder. Mevcut Monadik desugaring do
bloklarına desugaring ile ikame edilebilir bindIO
, returnIO
ve failIO
tarif 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 . bar
daktilo kontrol etmez, çünkü b
veMaybe b
aynı tip değildir.
Ama ... neredeyse doğru. Sadece biraz boşluk istiyorsun. Temelmiş Maybe b
gibi 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 id
oluş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.
id . f
=f
f . id
=f
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 Monad
dilde 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.
newtype Kleisli m a b = Kleisli (a -> m b)
. Kleisli kategorileri, kategorik döndürme türünün ( b
bu durumda) bir tür yapıcısına argüman olduğu işlevlerdir m
. Iff Kleisli m
bir kategori oluşturur m
, bir Monad'dır.
Kleisli m
eşyasını gelen oklar bu Haskell tip ve bir kategori oluşturur görünmektedir a
için b
işlevleri vardır a
için m b
olan, id = return
ve (.) = (<=<)
. Bu doğru mu, yoksa farklı seviyelerde şeyleri mi karıştırıyorum?
a
ve b
basit işlevler değildir. m
Fonksiyonun dönüş değerinde ekstra ile dekore edilmiştir .
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 filter
fonksiyonu kullanmaktır .filter (> 3) [1..10]
[4,5,6,7,8,9,10]
Bir filter
akü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] < 25
yazabiliriz ki
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
eşittir [3,4,5,6]
.
Veya nub
yinelenen öğ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 notElem
kullanmaz >>=
, 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.Monad
modü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)
filterM
bir 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
StateT
sadece 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.
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.
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 IO
sadece 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!
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.
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.
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, bind
ve return
aynı zamanda monad yasaları olarak adlandırılan, iki denklem (sol ve sağ kimlikler) yerine getirmelidir. (Alternatif olarak, monadları flattening operation
bağ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.
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.
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
, String
ve Real
ve tip fonksiyonları Int -> String
, String -> Real
ve 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. .
Maybe
Bir tür yapıcı olduğuna dikkat edin . Gibi bir tür alır Int
ve 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 String
veString -> 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 = == .