Bir monad görmenin farklı yolları


29

Haskell'i öğrenirken, Monad'ın ne olduğunu ve Haskell'de neden monadların önemli olduğunu açıklamaya çalışan birçok dersle karşılaştım. Her biri analojiyi kullandı, bu yüzden anlamı yakalamak daha kolaydı. Günün sonunda, bir monadın ne olduğuna dair 3 değişik görüş ortaya çıktı:

1 görüntüle: Monad bir etiket olarak

Bazen bir monadın belirli bir tip için bir etiket olduğunu düşünüyorum. Örneğin, bir tür işlevi:

myfunction :: IO Int

myfunction, ne zaman gerçekleştirilirse bir Int değeri vereceği bir fonksiyondur. Sonucun türü Int değil IO Int'dir. Bu nedenle, IO, Int değerinin bir GÇ eyleminin yapıldığı bir işlemin sonucu olduğunu bilmesi için kullanıcıyı uyaran bir Int değeri etiketidir.

Sonuç olarak, bu Int değeri IO olan bir işlemden gelen değer olarak işaretlenmiştir, bu nedenle bu değer "kirli" dir. Prosesiniz artık saf değil.

2. Görünümü: Pis şeylerin olabileceği özel bir alan olarak Monad.

Tüm sürecin saf ve katı olduğu bir sistemde, bazen yan etkilere ihtiyaç duyarsınız. Yani, bir monad kötü yan etkileri yapmak için size izin veren sadece küçük bir alandır. Bu alanda, saf dünyadan kaçma, saf olmayanlara gitme, sürecini yapma ve daha sonra bir değerle geri dönme izniniz var.

Görünüm 3: Kategori teorisinde olduğu gibi Monad

Bu tam olarak anlamadığım bir görüş. Bir monad aynı kategoriye veya bir alt kategoriye sadece bir işlevdir. Örneğin, bir IO işleminden sonra oluşturulan Int değerleri olan Int değerlerine ve IO Int alt kategorisine sahip olursunuz.

Bu görüşler doğru mu? Hangisi daha doğru?


5
# 2 genel olarak bir monadın ne olduğu değildir. Aslında, IO ile sınırlıdır ve yararlı bir görüş değildir (bkz. Monad değildir ). Ayrıca, "katı" genellikle Haskell'in sahip olmadığı bir mülkü adlandırmak için alınır (yani katı değerlendirme). Bu arada, Monad'lar da bunu değiştirmez (yine, Monadın ne olmadığıdır).

3
Teknik olarak, sadece üçüncüsü doğrudur. Monad endofunctor, özel operasyon için tanımlandığı - promosyon ve bağlayıcı. Monadlar çoktur - bir liste monad, monadların arkasındaki sezgiyi elde etmek için mükemmel bir örnektir. readS tesisleri daha da iyi. Yeterince şaşırtıcı bir şekilde, monadlar saf işlevsel dilde dolaylı olarak durum sergilemek için araçlar olarak kullanılabilir. Bu, monadların tanımlayıcı bir özelliği değildir: tesadüf eseridir, devlet ipliğinin terimleriyle uygulanabilir olması. Aynısı IO için de geçerlidir.
permeakra

Common Lisp, dilin bir parçası olarak kendi derleyicisine sahiptir. Haskell'in Monad'ları var.
Ness

Yanıtlar:


33

# 1 ve # 2 görünümleri genel olarak hatalı.

  1. Her tür veri türü * -> *bir etiket olarak çalışabilir, monad'lar bundan çok daha fazlasıdır.
  2. ( IOMonad 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 IOda, 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 -> bve g :: b -> calı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 :: ave 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 -> aa.ff . idid . 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 mtü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, molduğu [], IO, Maybevs.) 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 bve 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 <=< returnbunun aynı fve aynı olmasını istiyoruz return <=< f.

Herhangi m :: * -> *kendisi için böyle fonksiyonlara sahip returnve <=<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 ave türlerin işlevlerini oluştururuz a -> m b.

Yarattığımız her monad için, bunu kontrol etmeyi returnve <=<istediğimiz özelliklere sahip olmayı unutmamalıyız : ilişkilendirme ve sol / sağ kimlik. İfade kullanarak returnve >>=onlar denir monad yasaları .

Bir örnek - listeler

Biz seçerseniz molmak [], 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], gher 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 IOveya ister State), fakat bazıları []veya ister Maybe.

IO monad

Bahsettiğim gibi, IOmonad 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.aIO aIO

İçin IOmonad:

  • Kompozisyonu f :: a -> IO bve g :: b -> IO canlamı: fÇevre ile etkileşime giren hesaplama , sonrag değeri kullanan ve çevre ile etkileşime giren sonucu hesaplayan hesaplama .
  • returnsadece 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:

  1. Monadik hesaplamalar her zaman sonuç türüne sahip m aolduğundan, IOmonaddan "kaçmanın" yolu yoktur . Bunun anlamı şudur: Bir hesaplama çevre ile etkileşime girdiğinde, ondan bir hesaplama yapamazsınız.
  2. İşlevsel bir programcı bir şeyi saf bir şekilde nasıl yapabileceğini bilmediğinde, (ler) ( son çare olarak) görevi IOmonad içindeki bazı durumsal hesaplamalarla programlayabilir . Bu nedenle IOgenellikle bir programcının günah kutusu denir .
  3. 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 getCharbir sonuç türü olması gerekir IO something.

3
Mükemmel cevap. IODil açısından özel bir anlam bilmediğine açıklık getirdim . Öyle değil başka herhangi bir kod gibi davranır, özel. Yalnızca çalışma zamanı kitaplığı uygulaması özeldir. Ayrıca kaçmak için özel bir yol var ( unsafePerformIO). Bunun önemli olduğunu düşünüyorum çünkü insanlar genellikle IOözel bir dil öğesi ya da bildirim etiketi olarak düşünürler . O değil.
usr

2
@ usr İyi nokta. Bunu eklersiniz unsafePerformIO gerçekten güvensiz ve sadece uzmanlar tarafından kullanılmalıdır. Her şeyi kırmanıza olanak tanır, örneğin, coerce :: a -> biki türden birini dönüştüren bir işlev oluşturabilirsiniz (ve çoğu durumda programınızı çökertebilirsiniz). Bu örneğe bakın - bir işlevi bile Intvb
.'ye

Başka bir "özel sihir" monad'ı ST'dir; bu da, okuyabileceğiniz ve uygun gördüğünüz şekilde yazabildiğiniz belleğe (sadece monad içinde olmasına rağmen) yazabildiğiniz referansları bildirmenize izin verir ve sonra arayarak bir sonucu çıkarabilirsinizrunST :: (forall s. GHC.ST.ST s a) -> a
sara

5

1 görüntüle: Monad bir etiket olarak

“Sonuç olarak, bu Int değeri IO ile bir süreçten gelen değer olarak işaretlendi, bu nedenle bu değer“ kirli ”.”

"IO Int" genel olarak bir Int değeri değildir (bazı durumlarda "return 3" gibi olsa da). Bazı Int değerleri veren bir prosedür. Bu "prosedürün" farklı uygulamaları farklı Int değerleri verebilir.

Bir monad m, gömülü (zorunlu) bir "programlama dili" dir: bu dilde bazı "prosedürleri" tanımlamak mümkündür. Bir monadik değer (ma tipi), bu "programlama dili" nde bir a tipi değeri veren bir prosedürdür.

Örneğin:

foo :: IO Int

Int türünde bir değer veren bir işlemdir.

Sonra:

bar :: IO (Int, Int)
bar = do
  a <- foo
  b <- foo
  return (a,b)

İki (muhtemelen farklı) Ints veren bir prosedürdür.

Böyle her "dil" bazı işlemleri desteklemektedir:

  • iki prosedür (ma ve mb) "birleştirilebilir" olabilir: birinciden sonra ikinciden yapılan daha büyük bir prosedür (ma >> mb) oluşturabilirsiniz;

  • dahası, ilki (a) çıktısı ikincisini etkileyebilir (ma >> = \ a -> ...);

  • Bir prosedür (x dönüşü) bazı sabit değerler (x) verebilir.

Farklı gömülü programlama dilleri, destekledikleri türlere göre farklılık gösterir:

  • rastgele değerler vererek;
  • “çatallama” ([] monad);
  • istisnalar (fırlatma / yakalama) (Her ikisi de monad);
  • açık bir devam / callcc desteği;
  • diğer "ajanlara" mesaj gönderme / alma;
  • değişkenleri yarat, ayarla ve oku (bu programlama dilinde yerel) (ST monad).

1

Monadic tipini monad sınıfı ile karıştırmayın.

Bir monadik tip (yani monad sınıfının bir örneği olan bir tip) belirli bir sorunu çözecektir (prensipte her monadik tip farklı bir problemi çözer): State, Random, Maybe, IO. Hepsi bağlamı olan türlerdir (“etiket” dediğiniz şey, ancak onları monad yapan şey bu değildir).

Hepsi için, “seçim ile zincirleme operasyonlar” ihtiyacı vardır (bir operasyon önceki sonucuna bağlıdır). Monad sınıfı devreye giriyor: türünüzün (belirli bir problemi çözerek) monad sınıfının bir örneği olması ve zincirleme probleminin çözülmesi.

Bkz monad sınıfı ne çözer?

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.