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.
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.
Yanıtlar:
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: Error
Monad 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
, digit
vb. 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. async
Monad 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 async
monad 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 bind
operatö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
"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 .
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 -> b
o 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
bind
biraz daha fazlasını yapabilir fmap
, ancak tam tersi olamaz. Aslında, fmap
sadece açısından tanımlanabilir bind
ve return
. Yani, bir monad tanımlarken .. onun türünü (burada Wrapped a
) ve sonra onun return
ve bind
operasyonları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.
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.
Monad, iki işlemi olan bir veri tipidir: >>=
(aka bind
) ve return
(aka unit
). return
isteğ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 return
değ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 Just
ve Nothing
olan Maybe
kurucular. 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.
Ö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 T
daha 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ş a
ve b
bir işlev) T a -> T b
. Bu map
işlev aynı zamanda kimlik ve kompozisyon yasalarına uymalıdır, böylece aşağıdaki ifadeler herkes için geçerli olur p
ve q
(Haskell notasyonu):
map id = id
map (p . q) = map p . map q
Örneğin, yukarıdaki yasalara uyan List
tü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 b
iş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 T
iki ekstra yöntemlerle, join
, tip T (T a) -> T a
ve unit
(bazen return
, fork
ya da pure
türden) a -> T a
. Haskell'deki listeler için:
join :: [[a]] -> [a]
pure :: a -> [a]
Bu neden faydalı? Çünkü, örneğin, map
bir liste döndüren bir işleve sahip bir liste üzerinden yapabilirsiniz. Join
sonuçta ortaya çıkan liste listesini alır ve sıralar. List
bir monad çünkü bu mümkün.
Beni anlayan bir fonksiyon yazabiliriz map
sonra join
. Bu fonksiyon denir bind
ya flatMap
, ya (>>=)
, ya (=<<)
. Haskell'de normalde böyle bir monad örneği verilir.
Bir monad, belirli yasaları yerine getirmek join
zorundadı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 pure
bir kimlik olmalı .join
join (pure x) == x
[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:
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 Maybe
veya bir olabilir IO
.
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 Int
bir olabilir Just Int
ya Nothing
. Artık bir eklerseniz, Maybe Int
bir karşı Maybe Int
, operatör ikisi de olup olmadığını görmek için kontrol edecektir Just Int
içerde, ve eğer öyleyse, ambalajından edecek Int
, s onları toplama operatörünü geçmesi, elde yeniden sarın Int
yeni içine Just Int
geçerli olan ( Maybe Int
) ve dolayısıyla a Maybe Int
. Ama eğer Nothing
içlerinden biri içerideyse, bu operatör hemen geri dönecektir Nothing
, ki bu yine geçerlidir Maybe Int
. Bu şekilde, s'nizin Maybe Int
normal 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 IO
için tanımlanan özel işleç IO
çağrılır ve ekleme yapılmadan önce tamamen farklı bir şey yapabilirdi. (Tamam, iki IO Int
s'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.
M (M a) -> M a
. Bunu bir tür M a -> (a -> M b) -> M b
haline getirebilmeniz, onları kullanışlı kılan şeydir.
Ç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:
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?
consolestate
Bu 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 lift
normal 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.
Birkaç yıl önce bu soruya bir cevap verdikten sonra, bu yanıtı geliştirip basitleştirebileceğime inanıyorum ...
Bir monad, kompozisyon bind
sı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 y
değ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ü bool
geç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 bool
bir demetin bölümünü inceleyebilecektir .
(Ma -> Mb) >> (Mb -> Mc)
Burada yine, kompozisyon doğal olarak gerçekleşir compose
ve 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_OK
olur 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 composition
yerine bir işlev kullanabiliriz compose
. Bu nedenle, değerleri bir fonksiyonun çıkışından diğerinin girişine aktarmak yerine, bir kısmını Bind
inceleyecek 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ıkM
Ma
a
bind
M
a
bind
a
M
ya 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 content
kabuğun, oluşturulan işlevin uygulanmasıyla ilgili veriler içerdiği ve yalnızca bind
işlev tarafından sorgulandığı ve yalnızca işlev tarafından kullanılabilir kaldığı bir modeldir .
Bu nedenle, bir monad üç şeydir:
M
monad ile ilgili bilgileri tutmak için bir kabuk, bind
bu 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 a -> Mb
monadik 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 Mb
sonuç 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 monad
s, değerleri a
uygulamadan sonra sararak değerleri, monadik tipe Ma
ve genel fonksiyonları a -> b
monadik 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)
bind
Fonksiyonun 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)
Associativity
bind
ne zaman bind
uygulanırsa uygulansın değerlendirme sırasını koruyan anlamına gelir . Olduğunu, tanımında Associativity
parantez yürürlüğe yukarıda erken değerlendirmenin binding
ait f
ve g
sadece beklediği bir işleve neden olacaktır Ma
sırayla tamamlamak için bind
. Dolayısıyla, Ma
değerinin uygulanabilmesi f
ve sonuçta uygulanabilmesi için değerlendirilmesi yapılmalıdır g
.
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:
(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.
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. if
Monad 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.
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.
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.
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ı.
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.
Monadlar Metafor Değildir , ancak Daniel Spiewak'ın açıkladığı gibi ortak bir modelden ortaya çıkan pratik olarak yararlı bir soyutlamadır.
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 data
ve veriler borulardan akıyor.
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.
Doğru anladıysam, IEnumerable monad'lardan türetilir. Acaba bu C # dünyasından bizler için ilginç bir yaklaşım açısı olabilir mi?
Değer için, işte bana yardımcı olan bazı öğreticiler için bağlantılar (ve hayır, hala monadların ne olduğunu anlamadım).
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.
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.
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, map
ve 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
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, "">
f
formun 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 f
ve g
bundan sorumlu olmanızdan hoşlanmıyorsunuz . (Tartışmanın uğruna hayal yerine dizeleri ekleyerek, f
ve 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 m
içine f
". To beslemek bir çift <x, messages>
bir işlev içine f
etmektir geçmesi x
halinde f
, elde <y, message>
dışına f
ve 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 x
ve 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 g
beslemek 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 t
ve dize" türünde bir değerdir . Bu türü arayın M t
.
M
bir tür yapıcısıdır: M
tek başına bir türe başvurmaz, ancak M _
boşluğu bir türle doldurduktan sonra bir türe başvurur . An M int
bir ç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.feed
a (a alan t
ve a döndüren bir işlev M u
) ve a alır ve an M t
döndürür M u
.wrap
a alır v
ve döndürür M v
.t
,, u
ve v
aynı 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ış t
bir işlev ile aynı geçen kullanıcıya t
işlevi.
resmen: feed(f, wrap(x)) = f(x)
M t
İçine bir beslemek için wrap
hiçbir şey yapmaz M t
.
resmen: feed(wrap, m) = m
Bir M t
(çağırma m
) fonksiyonunu
t
içine geçerg
M u
(diyoruz n
) dang
n
içine beslenirf
aynıdır
m
içine beslemekg
n
-den almakg
n
içine beslemekf
Resmen: feed(h, m) = feed(f, feed(g, m))
neredeh(x) := feed(f, g(x))
Tipik feed
olarak bind
( >>=
Haskell'de AKA ) wrap
denir ve denir return
.
Monad
Haskell 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 -> String
ve f :: String -> Bool
.
Biz yapabiliriz (f . g) x
sadece aynı olan f (g x)
yerlerde, x
bir olduğunu Int
değer.
Kompozisyon yaparken / bir fonksiyonun sonucunu diğerine uygularken, türlerin eşleşmesi önemlidir. Yukarıdaki durumda, döndürülen sonucun g
tü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 Int
tür Int
orada bulunmayan IO String
bir String
değeri , tür bazı yan etkilerin gerçekleştirilmesinin sonucu olarak orada bulunan bir değeri temsil eder .)
Diyelim ki şimdi g1 :: Int -> Maybe String
ve f1 :: String -> Maybe Bool
. g1
ve f1
çok benzer g
ve f
sırasıyla.
Yapamayız (f1 . g1) x
veya f1 (g1 x)
nerede x
bir Int
değerdir. Tarafından döndürülen sonucun türü beklendiği gibi g1
değil f1
.
Yazabilir f
ve operatörle g
birlikte olabiliriz .
, ama şimdi yazamayız f1
ve g1
ile 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 g1
ve f1
yazabilmemiz iyi olmaz (f1 OPERATOR g1) x
mıydı? g1
bağ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 x
bir Maybe Int
değerdir. >>=
Operatör o almaya yardımcı olur Int
"belki-not-orada" bağlam dışına değerini ve uygulamak f1
. Sonucu f1
a,, Maybe Bool
tüm sonucu olacaktır >>=
işlem.
Ve son olarak, neden Monad
faydalıdır? Çünkü operatörü Monad
tanımlayan tip sınıfı, ve operatörünü tanımlayan tip sınıfı ile >>=
aynıdır .Eq
==
/=
Sonuç olarak, Monad
type 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 Monad
bağlamlarda değerleri içeren fonksiyon kompozisyonuna izin vermesidir .
{-# 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
$
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, id
sağ ve sol kimliğidir.
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 f
tü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 r
oluş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 a
ve 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 t
Monadik bağlam içine m
. Uzatma veya Kleisli uygulaması bir hesaplama sonucuna =<<
Kleisli oku uygular .a -> m b
m 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, return
sağ ve sol kimliğidir.
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
Bir seçenek türü
data Maybe t = Nothing | Just t
Maybe t
mutlaka sonuç vermeyen t
hesaplamayı, “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 b
bir 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 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 x
bir Kleisli ait ok a -> [b]
elemanlarına [a]
tek sonuç listesine [b]
.
Pozitif tamsayı uygun bölenler Let n
olmak
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 fail
ve <|>
bir monoid oluştururforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
ve fail
ilave monadların emici / yok edici sıfır elementidir
_ =<< fail = fail
Eğer varsa
guard (even p) >> return p
even p
doğ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 p
atlanır.
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 st
ve 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 get
ve durum bilgisi olan monadlara put
gö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 -> st
devlet tipinin monad'a işlevsel bir bağımlılığını beyan eder ; örneğin a , durum tipinin benzersiz olacağını belirler .st
m
State t
t
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
void
C 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
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.
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 C
bir kategoriye bir eşlemedir D
; içindeki C
bir nesnedeki her nesne içinD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
ve her C
bir morfizm içinD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
nerede X
, Y
nesneler içinde C
. HomC(X, Y)
olduğu homomorfizmi sınıfı tüm Morfizmlerin X -> Y
içinde C
. Funktoru morfizmanın kimlik ve bileşimin, bir “yapı” korumak zorundadır C
olarak, 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 C
bir 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)
Hask
Kleisli kategorisinde bir morfizm verildi
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
Kleisli kategorisindeki kompozisyon .T
uzatma 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 mu
içinde denilen programlaması join
. Bir monad, mu
bir 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
mu
Seçenek monadının kanonik uygulaması:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
concat
fonksiyon
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
olduğu join
liste 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
mu
Eklenti formuna tersten çeviri şu şekilde yapılır:
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler: Fonksiyonel programlama için Monad'lar
Simon L Peyton Jones, Philip Wadler: Zorunlu işlevsel programlama
Jonathan MD Hill, Keith Clarke: Kategori teorisi, kategori teorisi monadları ve bunların fonksiyonel programlama ile ilişkisi ´
Eugenio Moggi: Hesaplama ve monad kavramları
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
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.
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.
http://code.google.com/p/monad-tutorial/ tam olarak bu soruyu ele almak için devam eden bir çalışmadır.
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,, f
bir 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, f
bir 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 f
biliyor, 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 a
reklamı 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ı f
birden fazla çıkış kombine edilmesi sureti ile. Bu örneği, >>=
ayıklamadan sorumlu a
olsa da, aynı zamanda nihai sınır çıktısına da erişimi olduğunu göstermek için ekliyorum f
. Gerçekten de, a
nihai çı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. IO
Monad aslında bir yok a
, ama bir adam bilir ve bu alacak a
senin için. State st
Monad, gizli bir yer alır st
o ileteceği f
rağmen masanın altında f
sadece soran geldi a
. Reader r
Monad benzer State st
sadece sağlayan rağmen, f
bakmak 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, f
artık ne yapacağına karar vermez Nothing
; kodlanmış >>=
. Bu değiş tokuş. İçin gerekli idiyse f
karar vermek durumunda ne yapılacağını Nothing
, sonra f
bir fonksiyon olması gerekirdi Maybe a
için Maybe b
. Bu durumda, Maybe
bir 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.
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.