# 1 ve # 2 görünümleri genel olarak hatalı.
- Her tür veri türü
* -> *
bir etiket olarak çalışabilir, monad'lar bundan çok daha fazlasıdır.
- (
IO
Monad hariç ) Bir monad içindeki hesaplamalar kesin değildir. Onlar sadece yan etkileri olduğunu düşündüğümüz hesaplamaları temsil ediyorlar, ancak saflar.
Her iki yanlış anlama IO
da, aslında biraz özel olan monata odaklanmaktan kaynaklanıyor .
Mümkünse kategori teorisine girmeden, # 3'ü biraz daha ayrıntılı olarak ele almaya çalışacağım.
Standart hesaplamalar
Fonksiyonel bir programlama dilinde Tüm hesaplamalar bir kaynak türü ve hedef türüyle fonksiyonları olarak görülebilir: f :: a -> b
. Bir işlevin birden fazla argümanı varsa, onu körleyerek bir tek argüman işlevine dönüştürebiliriz (ayrıca bkz . Haskell wiki ). Biz sadece bir değeri varsa x :: a
(0 argümanlarla bir işlev), biz bir argüman alan bir fonksiyon haline dönüştürebilirsiniz birimi türü : (\_ -> x) :: () -> a
.
.
Operatörü kullanarak bu fonksiyonları oluşturarak daha karmaşık programları daha karmaşık programlar oluşturabiliriz . Mesela bizde varsa f :: a -> b
ve g :: b -> c
alırsak g . f :: a -> c
. Bunun bizim dönüştürülmüş değerlerimiz için de işe yaradığını unutmayın: Eğer varsa x :: a
ve gösterime dönüştürürsek, anlıyoruz f . ((\_ -> x) :: () -> a) :: () -> b
.
Bu gösterimin bazı çok önemli özellikleri vardır:
- Çok özel bir işleve sahibiz - her türün kimlik işlevi . Bir olan kimlik elemanı ile ilgili olarak : her iki eşittir ve .
id :: a -> a
a
.
f
f . id
id . f
- Fonksiyon bileşim operatörü
.
olan birleştirici .
Monadic hesaplamalar
Diyelim ki sonucu sadece tek bir geri dönüş değerinden daha fazlasını içeren bazı özel hesaplama kategorileri seçmek ve onlarla çalışmak istiyoruz. "Başka bir şeyin" ne anlama geldiğini belirtmek istemiyoruz, olayları mümkün olduğu kadar genel tutmak istiyoruz. " Başka bir şeyi" temsil etmenin en genel yolu, onu bir tür işlevi olarak (bir m
tür * -> *
diğerine dönüştürür) bir tür işlevi olarak göstermektir . Bu nedenle birlikte çalışmak istediğimiz her bir hesaplama kategorisi için bir tür fonksiyona sahip olacağız m :: * -> *
. (In Haskell, m
olduğu []
, IO
, Maybe
vs.) Ve kategori irade türlerinin tüm fonksiyonları içerir a -> m b
.
Şimdi böyle bir kategorideki fonksiyonlarla temel durumdaki gibi çalışmak istiyoruz. Bu işlevleri oluşturabilmek istiyoruz, kompozisyonun birleştirici olmasını istiyoruz ve bir kimliğe sahip olmak istiyoruz. İhtiyacımız var:
<=<
İşlevleri f :: a -> m b
ve bir g :: b -> m c
şeyleri oluşturan bir operatöre sahip olmak (onu diyelim ) g <=< f :: a -> m c
. Ve, birleştirici olmalı.
- Her tür için bir kimlik işlevi olması için, onu çağıralım
return
. Biz de f <=< return
bunun aynı f
ve aynı olmasını istiyoruz return <=< f
.
Herhangi m :: * -> *
kendisi için böyle fonksiyonlara sahip return
ve <=<
bir denir monad . Temel durumda olduğu gibi, basit olanlardan karmaşık hesaplamalar oluşturmamıza olanak tanır, ancak şimdi dönüş değerleri türleri tarafından değiştirilir m
.
(Aslında, buradaki kategori terimini biraz kötüye kullandım . Kategori-teorik anlamda, inşaatımıza bir kategori diyebiliriz, ancak bu yasalara uyduğunu öğrendikten sonra.)
Haskell'deki Monadlar
Haskell'de (ve diğer işlevsel dillerde) çoğunlukla türlerin işlevleriyle değil, değerlerle çalışırız () -> a
. Dolayısıyla <=<
her monad için tanım yapmak yerine, bir fonksiyon tanımlarız (>>=) :: m a -> (a -> m b) -> m b
. Böyle bir alternatif tanım eşittir, >>=
kullanmayı <=<
ve bunun tersini ifade edebiliriz (bir egzersiz olarak deneyin ya da kaynakları görün ). İlke şimdi daha az açıktır, ancak aynı kalır: Sonuçlarımız daima türlerdir m a
ve türlerin işlevlerini oluştururuz a -> m b
.
Yarattığımız her monad için, bunu kontrol etmeyi return
ve <=<
istediğimiz özelliklere sahip olmayı unutmamalıyız : ilişkilendirme ve sol / sağ kimlik. İfade kullanarak return
ve >>=
onlar denir monad yasaları .
Bir örnek - listeler
Biz seçerseniz m
olmak []
, biz türleri fonksiyonların bir kategori olsun a -> [b]
. Bu tür işlevler, sonuçları bir veya daha fazla değer olabilen, aynı zamanda değeri olmayan belirleyici olmayan hesaplamaları temsil eder. Bu sözde liste monad yol açar . Kompozisyonu f :: a -> [b]
ve g :: b -> [c]
aşağıdaki gibi çalışır: g <=< f :: a -> [c]
olası tüm sonuçları hesaplamak [b]
, g
her birine uygulamak ve tüm sonuçları tek bir listede toplamak demektir. Haskell ile ifade edilir
return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f = concat . map g . f
veya kullanma >>=
(>>=) :: [a] -> (a -> [b]) -> [b]
x >>= f = concat (map f x)
Bu örnekte, geri dönüş türlerinin, [a]
herhangi bir tür değer içermemesinin mümkün olduğunu unutmayın a
. Gerçekten de, bir monad için dönüş türünün bu gibi değerlere sahip olması gerektiği gibi bir gereklilik yoktur. Bazı monad'lar her zaman (ister IO
veya ister State
), fakat bazıları []
veya ister Maybe
.
IO monad
Bahsettiğim gibi, IO
monad biraz özeldir. Bir tür değeri , programın ortamı ile etkileşime girerek oluşturulan bir tür değeriIO a
anlamına gelir . Bu yüzden (diğer tüm monadların aksine), bazı saf yapılar kullanarak bir tür değeri tanımlayamayız . İşte sadece çevre ile etkileşime giren hesaplamaları ayıran bir etiket veya etiket. Bu # 1 ve # 2 görünümlerinin doğru olduğu tek durumdur.a
IO a
IO
İçin IO
monad:
- Kompozisyonu
f :: a -> IO b
ve g :: b -> IO c
anlamı: f
Çevre ile etkileşime giren hesaplama , sonrag
değeri kullanan ve çevre ile etkileşime giren sonucu hesaplayan hesaplama .
return
sadece IO
"tag" değerini değere ekler (çevreyi bozulmadan tutarak sonucu "hesaplarız").
- Monad yasaları (birleşme, kimlik) derleyici tarafından garanti edilir.
Bazı notlar:
- Monadik hesaplamalar her zaman sonuç türüne sahip
m a
olduğundan, IO
monaddan "kaçmanın" yolu yoktur . Bunun anlamı şudur: Bir hesaplama çevre ile etkileşime girdiğinde, ondan bir hesaplama yapamazsınız.
- İşlevsel bir programcı bir şeyi saf bir şekilde nasıl yapabileceğini bilmediğinde, (ler) ( son çare olarak) görevi
IO
monad içindeki bazı durumsal hesaplamalarla programlayabilir . Bu nedenle IO
genellikle bir programcının günah kutusu denir .
- Saf olmayan bir dünyada (işlevsel programlama anlamında) bir değer okumanın çevreyi de değiştirebileceğine dikkat edin (kullanıcının girdisini tüketmek gibi). Bu nedenle gibi işlevlerin
getChar
bir sonuç türü olması gerekir IO something
.