Cos'è una monade?


1415

Dopo aver esaminato brevemente Haskell di recente, quale sarebbe una breve, concisa, concreta spiegazione di cosa sia essenzialmente una monade?

Ho trovato la maggior parte delle spiegazioni che ho scoperto essere abbastanza inaccessibili e prive di dettagli pratici.


12
Eric Lippert ha scritto una risposta a queste domande ( stackoverflow.com/questions/2704652/… ), che a causa di alcuni problemi risiede in una pagina separata.
P:

70
Ecco una nuova introduzione usando JavaScript: l'ho trovata molto leggibile.
Benjol,



2
Una monade è una matrice di funzioni con operazioni di aiuto. Vedi questa risposta
cibercitizen1,

Risposte:


1060

Primo: il termine monade è un po 'vacuo se non sei un matematico. Un termine alternativo è costruttore di calcoli che è un po 'più descrittivo di ciò per cui sono effettivamente utili.

Chiedete esempi pratici:

Esempio 1: comprensione dell'elenco :

[x*2 | x<-[1..10], odd x]

Questa espressione restituisce i doppi di tutti i numeri dispari nell'intervallo da 1 a 10. Molto utile!

Si scopre che questo è davvero solo zucchero sintattico per alcune operazioni all'interno della monade List. La stessa comprensione dell'elenco può essere scritta come:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

O anche:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Esempio 2: Input / Output :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Entrambi gli esempi usano monadi, costruttori di calcolo AKA. Il tema comune è che le catene di monade operano in un modo specifico e utile. Nella comprensione dell'elenco, le operazioni sono concatenate in modo tale che se un'operazione restituisce un elenco, le seguenti operazioni vengono eseguite su ogni elemento dell'elenco. D'altra parte, la monade IO esegue le operazioni in sequenza, ma passa una "variabile nascosta", che rappresenta "lo stato del mondo", che ci consente di scrivere il codice I / O in modo puro e funzionale.

Si scopre che il modello delle operazioni di concatenamento è abbastanza utile e viene utilizzato per molte cose diverse in Haskell.

Un altro esempio sono le eccezioni: usando la Errormonade, le operazioni sono concatenate in modo tale da essere eseguite in sequenza, tranne se viene generato un errore, nel qual caso il resto della catena viene abbandonato.

Sia la sintassi di comprensione dell'elenco che la notazione sono zucchero sintattico per operazioni di concatenamento che utilizzano l' >>=operatore. Una monade è fondamentalmente solo un tipo che supporta l' >>=operatore.

Esempio 3: un parser

Questo è un parser molto semplice che analizza una stringa tra virgolette o un numero:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Le operazioni char, digitecc. Sono piuttosto semplici. O corrispondono o non corrispondono. La magia è la monade che gestisce il flusso di controllo: le operazioni vengono eseguite in sequenza fino a quando una partita fallisce, nel qual caso la monade torna all'ultimo <|>e prova l'opzione successiva. Ancora una volta, un modo per concatenare le operazioni con qualche semantica aggiuntiva e utile.

Esempio 4: programmazione asincrona

Gli esempi sopra riportati sono in Haskell, ma risulta che F # supporta anche le monadi. Questo esempio è stato rubato da Don Syme :

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() }

Questo metodo recupera una pagina Web. La punch line è l'uso di GetResponseAsync- in realtà attende la risposta su un thread separato, mentre il thread principale ritorna dalla funzione. Le ultime tre righe vengono eseguite sul thread generato quando la risposta è stata ricevuta.

Nella maggior parte delle altre lingue dovresti creare esplicitamente una funzione separata per le linee che gestiscono la risposta. La asyncmonade è in grado di "dividere" il blocco da solo e rimandare l'esecuzione della seconda metà. (La async {}sintassi indica che il flusso di controllo nel blocco è definito dalla asyncmonade.)

Come funzionano

Quindi come può una monade fare tutte queste fantasiose cose sul flusso di controllo? Ciò che realmente accade in un do-block (o in un'espressione di calcolo come sono chiamati in F #), è che ogni operazione (praticamente ogni riga) è racchiusa in una funzione anonima separata. Queste funzioni vengono quindi combinate utilizzando l' bindoperatore (scritto >>=in Haskell). Poiché l' bindoperazione combina le funzioni, può eseguirle come ritiene opportuno: in sequenza, più volte, al contrario, scartane alcune, eseguine alcune su un thread separato quando ne hai voglia e così via.

Ad esempio, questa è la versione espansa dell'IO-code dell'esempio 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Questo è più brutto, ma è anche più ovvio ciò che sta realmente accadendo. L' >>=operatore è l'ingrediente magico: prende un valore (sul lato sinistro) e lo combina con una funzione (sul lato destro), per produrre un nuovo valore. Questo nuovo valore viene quindi preso dall'operatore successivo >>=e nuovamente combinato con una funzione per produrre un nuovo valore. >>=può essere visto come un mini-valutatore.

Nota che >>=è sovraccarico per diversi tipi, quindi ogni monade ha la sua implementazione di >>=. (Tuttavia, tutte le operazioni nella catena devono essere del tipo della stessa monade, altrimenti l' >>=operatore non funzionerà.)

L'implementazione più semplice possibile di >>=prende semplicemente il valore a sinistra e lo applica alla funzione a destra e restituisce il risultato, ma come detto prima, ciò che rende utile l'intero schema è quando c'è qualcosa in più nell'implementazione della monade di >>=.

Vi è una certa intelligenza aggiuntiva nel modo in cui i valori vengono passati da un'operazione all'altra, ma ciò richiede una spiegazione più approfondita del sistema di tipi Haskell.

Riassumendo

In termini di Haskell una monade è un tipo parametrizzato che è un'istanza della classe di tipo Monad, che definisce >>=insieme ad alcuni altri operatori. In parole povere, una monade è solo un tipo per il quale >>=è definita l' operazione.

Di per sé >>=è solo un modo ingombrante di concatenare le funzioni, ma con la presenza della notazione che nasconde "l'impianto idraulico", le operazioni monadiche si rivelano un'astrazione molto bella e utile, utile in molti luoghi della lingua e utile per creare le tue mini-lingue nella lingua.

Perché le monadi sono dure?

Per molti studenti di Haskell, le monadi sono un ostacolo che colpiscono come un muro di mattoni. Non è che le monadi stesse siano complesse, ma che l'implementazione si basi su molte altre funzionalità avanzate di Haskell come tipi con parametri, classi di tipi e così via. Il problema è che l'I / O di Haskell si basa su monadi e l'I / O è probabilmente una delle prime cose che vuoi capire quando impari una nuova lingua - dopo tutto, non è molto divertente creare programmi che non producono alcun produzione. Non ho una soluzione immediata per questo problema a base di galline e uova, se non trattando l'I / O come "la magia accade qui" fino a quando non avrai abbastanza esperienza con altre parti del linguaggio. Scusate.

Ottimo blog sulle monadi: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


66
Come persona che ha avuto molti problemi a capire le monadi, posso dire che questa risposta mi ha aiutato un po '. Tuttavia, ci sono ancora alcune cose che non capisco. In che modo la comprensione dell'elenco è una monade? Esiste una forma estesa di quell'esempio? Un'altra cosa che mi preoccupa davvero della maggior parte delle spiegazioni della monade, compresa questa, è che continuano a confondere "cos'è una monade?" con "a cosa serve una monade?" e "Come viene implementata una monade?". hai saltato quello squalo quando hai scritto "Una monade è fondamentalmente solo un tipo che supporta l'operatore >> =". Che mi aveva appena ...
Breton,

83
Inoltre non sono d'accordo con la tua conclusione sul perché le monadi siano difficili. Se le monadi stesse non sono complesse, dovresti essere in grado di spiegare cosa sono senza un sacco di bagagli. Non voglio conoscere l'implementazione quando faccio la domanda "Cos'è una monade", voglio sapere che prurito significa graffiare. Finora sembra che la risposta sia "Perché gli autori di Hashell sono sadomasochisti e hanno deciso che dovresti fare qualcosa di stupidamente complesso per realizzare cose semplici, quindi DEVI imparare le monadi per usare Hashell, non perché sono in alcun modo utili in stessi "...
Breton,

70
Ma .. non può essere giusto, vero? Penso che le monadi siano difficili perché nessuno riesce a capire come spiegarle senza farsi prendere da confusi dettagli di implementazione. Voglio dire ... che cos'è uno scuolabus? È una piattaforma metallica con un dispositivo nella parte anteriore che consuma un raffinato prodotto petrolifero per guidare in un ciclo alcuni pistoni metallici, che a loro volta ruotano un albero a gomito attaccato ad alcuni ingranaggi che guidano alcune ruote. Le ruote hanno intorno a sé sacchi di gomma gonfiati che si interfacciano con una superficie di asfalto per far avanzare una collezione di sedili. I posti si spostano in avanti perché ...
Breton,

130
Ho letto tutto questo e ancora non so cosa sia una monade, a parte il fatto che è qualcosa che i programmatori Haskell non comprendono abbastanza bene da spiegare. Gli esempi non aiutano molto, dato che sono tutte cose che si possono fare senza le monadi, e questa risposta non chiarisce come le monadi le rendono più facili, solo più confuse. L'unica parte di questa risposta che si è avvicinata all'essere utile è stata la rimozione dello zucchero sintattico dell'esempio n. 2. Dico che mi sono avvicinato perché, a parte la prima riga, l'espansione non ha alcuna reale somiglianza con l'originale.
Laurence Gonsalves il

81
Un altro problema che sembra endemico alle spiegazioni delle monadi è che è scritto in Haskell. Non sto dicendo che Haskell sia un brutto linguaggio - sto dicendo che è un brutto linguaggio per spiegare le monadi. Se avessi conosciuto Haskell avrei già capito le monadi, quindi se vuoi spiegare le monadi, inizia usando una lingua che le persone che non conoscono le monadi hanno maggiori probabilità di capire. Se devi usare Haskell, non usare affatto lo zucchero sintattico: usa il sottoinsieme più piccolo e semplice della lingua che puoi e non assumere una comprensione di Haskell IO.
Laurence Gonsalves il

712

Spiegare "cos'è una monade" è un po 'come dire "cos'è un numero?" Usiamo sempre numeri. Ma immagina di aver incontrato qualcuno che non sapeva nulla dei numeri. Come diamine spiegheresti quali sono i numeri? E come inizieresti a descrivere perché potrebbe essere utile?

Cos'è una monade? La risposta breve: è un modo specifico di concatenare le operazioni insieme.

In sostanza, stai scrivendo i passaggi di esecuzione e collegandoli insieme alla "funzione di associazione". (In Haskell, è chiamato >>=.) Puoi scrivere tu stesso le chiamate all'operatore di bind, oppure puoi usare lo zucchero di sintassi che fa sì che il compilatore inserisca quelle chiamate di funzione per te. In ogni caso, ogni passaggio è separato da una chiamata a questa funzione di associazione.

Quindi la funzione bind è come un punto e virgola; separa i passaggi in un processo. Il compito della funzione di associazione è quello di prendere l'output dal passaggio precedente e inserirlo nel passaggio successivo.

Non sembra troppo difficile, vero? Ma esiste più di un tipo di monade. Perché? Come?

Bene, la funzione di associazione può semplicemente prendere il risultato da un passaggio e inviarlo al passaggio successivo. Ma se è "tutto" la monade fa ... che in realtà non è molto utile. E questo è importante da capire: ogni utile monade fa qualcos'altro oltre ad essere solo una monade. Ogni monade utile ha un "potere speciale", che la rende unica.

(Una monade che non fa nulla di speciale si chiama "monade identitaria". Piuttosto come la funzione identitaria, sembra una cosa del tutto inutile, eppure risulta non essere ... Ma questa è un'altra storia ™.)

Fondamentalmente, ogni monade ha la propria implementazione della funzione di associazione. E puoi scrivere una funzione di bind in modo tale da mettere in comune le cose tra i passaggi di esecuzione. Per esempio:

  • Se ogni passaggio restituisce un indicatore di successo / fallimento, è possibile eseguire il bind eseguendo il passaggio successivo solo se quello precedente ha avuto esito positivo. In questo modo, un passaggio non riuscito interrompe "automaticamente" l'intera sequenza, senza alcun test condizionale da parte tua. (La monade fallita .)

  • Estendendo questa idea, è possibile implementare "eccezioni". (La Monade dell'errore o la Monade dell'eccezione .) Dato che le definisci tu stesso piuttosto che essere una funzione linguistica, puoi definire come funzionano. (Ad esempio, forse si desidera ignorare le prime due eccezioni e interrompere solo quando viene generata una terza eccezione.)

  • È possibile fare in modo che ogni passaggio restituisca più risultati e che la funzione di associazione sia ripetuta su di essi, inserendo ciascuno di essi nel passaggio successivo. In questo modo, non è necessario continuare a scrivere loop in tutto il luogo quando si ha a che fare con più risultati. La funzione di associazione "automaticamente" fa tutto questo per te. (The List Monad .)

  • Oltre a trasmettere un "risultato" da una fase all'altra, puoi anche far sì che la funzione di associazione passi dati extra . Questi dati ora non vengono visualizzati nel tuo codice sorgente, ma puoi comunque accedervi da qualsiasi luogo, senza doverlo passare manualmente a tutte le funzioni. (The Reader Monad .)

  • Puoi farlo in modo che i "dati extra" possano essere sostituiti. Ciò consente di simulare aggiornamenti distruttivi , senza effettivamente effettuare aggiornamenti distruttivi. (The State Monad e suo cugino Writer Monad .)

  • Poiché stai solo simulando aggiornamenti distruttivi, puoi fare banalmente cose che sarebbero impossibili con aggiornamenti distruttivi reali . Ad esempio, è possibile annullare l'ultimo aggiornamento o ripristinare una versione precedente .

  • È possibile creare una monade in cui è possibile mettere in pausa i calcoli , quindi è possibile mettere in pausa il programma, accedere e armeggiare con i dati di stato interni, quindi riprenderlo.

  • Puoi implementare "continuazioni" come monade. Questo ti permette di rompere le menti delle persone!

Tutto questo e molto altro è possibile con le monadi. Naturalmente, tutto ciò è perfettamente possibile anche senza le monadi. È drasticamente più semplice usare le monadi.


13
Apprezzo la tua risposta, in particolare la concessione finale che tutto ciò è ovviamente possibile anche senza monadi. Un punto da sottolineare è che per lo più è più facile con le monadi, ma spesso non è efficiente come farlo senza di loro. Una volta che è necessario coinvolgere i trasformatori, la sovrapposizione di chiamate di funzione (e oggetti funzione creati) ha un costo difficile da vedere e controllare, reso invisibile da una sintassi intelligente.
seh

1
Almeno in Haskell, la maggior parte delle spese generali delle monadi viene strappata via dall'ottimizzatore. Quindi l'unico vero "costo" è il potere del cervello richiesto. (Questo non è insignificante se la "manutenibilità" è qualcosa che ti interessa.) Ma di solito, le monadi rendono le cose più facili , non più difficili. (Altrimenti, perché dovresti preoccuparti?)
MathematicalOrchid

Non sono sicuro che Haskell lo supporti o meno, ma matematicamente puoi definire una monade in termini di >> = e restituire o unire e ap. >> = e return sono ciò che rende le monadi praticamente utili ma join e ap danno una comprensione più intuitiva di cosa sia una monade.
Elenco Jeremy

15
Provenendo da un background di programmazione non matematico e non funzionale, questa risposta ha avuto il senso per me.
jrahhali,

10
Questa è la prima risposta che mi ha dato un'idea di cosa diavolo è una monade. Grazie per aver trovato un modo per spiegarlo!
robot,

186

In realtà, contrariamente alla comprensione comune delle Monadi, non hanno nulla a che fare con lo stato. Le monadi sono semplicemente un modo per avvolgere le cose e forniscono metodi per fare operazioni sulle cose avvolte senza scartarle.

Ad esempio, puoi creare un tipo per avvolgerne un altro, in Haskell:

data Wrapped a = Wrap a

Per avvolgere le cose che definiamo

return :: a -> Wrapped a
return x = Wrap x

Per eseguire operazioni senza scartare, supponi di avere una funzione f :: a -> b, quindi puoi farlo per sollevare quella funzione per agire sui valori di wrapping:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Questo è tutto ciò che c'è da capire. Tuttavia, si scopre che esiste una funzione più generale per eseguire questo sollevamento , che è bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindpuò fare un po 'di più fmap, ma non viceversa. In realtà, fmappuò essere definito solo in termini di binde return. Così, quando si definisce una monade .. si dà il tipo (qui è stato Wrapped a) e poi dire come la sua returne bindle operazioni di lavoro.

La cosa interessante è che questo risulta essere un modello così generale che si manifesta ovunque, incapsulare lo stato in modo puro è solo uno di questi.

Per un buon articolo su come le monadi possono essere usate per introdurre dipendenze funzionali e quindi controllare l'ordine di valutazione, come è usato nella monade IO di Haskell, dai un'occhiata a IO Inside .

Per quanto riguarda la comprensione delle monadi, non preoccuparti troppo. Leggi su di loro ciò che trovi interessante e non preoccuparti se non capisci subito. Quindi semplicemente immergersi in una lingua come Haskell è la strada da percorrere. Le monadi sono una di queste cose in cui la comprensione penetra nel tuo cervello con la pratica, un giorno ti rendi improvvisamente conto di capirli.


-> è un'applicazione della funzione di mirroring associativa di destra, che è associativa di sinistra, quindi lasciare fuori le parentesi non fa differenza qui.
Matthias Benkard,

1
Non penso che questa sia un'ottima spiegazione. Le monadi sono semplicemente un modo? ok, da che parte? Perché non dovrei incapsulare usando una classe anziché una monade?
Bretone,

4
@ mb21: nel caso in cui stai solo sottolineando che ci sono troppe parentesi, nota che a-> b-> c è in realtà solo l'abbreviazione di a -> (b-> c). Scrivere questo esempio particolare come (a -> b) -> (Ta -> Tb) sta parlando in senso stretto semplicemente aggiungendo caratteri non necessari, ma è moralmente "la cosa giusta da fare" poiché sottolinea che la fmap mappa una funzione di tipo a -> b per una funzione di tipo Ta -> Tb. E originariamente, questo è ciò che i funzionali fanno nella teoria delle categorie ed è da lì che provengono le monadi.
Nikolaj-K,

1
Questa risposta è fuorviante. Alcune monadi non hanno affatto un "wrapper", una tale funzione da un valore fisso.

1
Le monadi @DanMandel sono modelli di progettazione che forniscono il proprio wrapper di tipo di dati. Le monadi sono progettate in modo da astrarre il codice della caldaia. Quindi quando chiami una Monade nel tuo codice, fa cose dietro le quinte di cui non vuoi preoccuparti. Pensa a Nullable <T> o IEnumerable <T>, cosa fanno dietro le quinte? Questa è Monade.
sksallaj,

168

Ma avresti potuto inventare Monadi!

sigfpe dice:

Ma tutti questi introducono le monadi come qualcosa di esoterico che ha bisogno di spiegazioni. Ma quello che voglio discutere è che non sono affatto esoterici. In effetti, di fronte a vari problemi nella programmazione funzionale, saresti stato condotto, inesorabilmente, a certe soluzioni, che sono tutti esempi di monadi. In effetti, spero di riuscire a inventarli adesso se non l'hai già fatto. È quindi un piccolo passo per notare che tutte queste soluzioni sono in realtà la stessa soluzione sotto mentite spoglie. E dopo aver letto questo, potresti essere in una posizione migliore per capire altri documenti sulle monadi perché riconoscerai tutto ciò che vedi come qualcosa che hai già inventato.

Molti dei problemi che le monadi cercano di risolvere sono legati al problema degli effetti collaterali. Quindi inizieremo con loro. (Nota che le monadi ti permettono di fare qualcosa di più che gestire gli effetti collaterali, in particolare molti tipi di oggetti contenitore possono essere visti come monadi. Alcune delle introduzioni alle monadi trovano difficile conciliare questi due diversi usi delle monadi e concentrarsi solo su uno o l'altro.)

In un linguaggio di programmazione imperativo come C ++, le funzioni non si comportano come le funzioni della matematica. Ad esempio, supponiamo di avere una funzione C ++ che accetta un singolo argomento in virgola mobile e restituisce un risultato in virgola mobile. Superficialmente potrebbe sembrare un po 'come una funzione matematica che associa i reali ai reali, ma una funzione C ++ può fare di più che restituire un numero che dipende dai suoi argomenti. Può leggere e scrivere i valori delle variabili globali, nonché scrivere l'output sullo schermo e ricevere input dall'utente. In un linguaggio funzionale puro, tuttavia, una funzione può solo leggere ciò che le viene fornito nei suoi argomenti e l'unico modo in cui può avere un effetto sul mondo è attraverso i valori che restituisce.


9
... il modo migliore non solo su Internet, ma ovunque. (Anche il documento originale di Wadler Monads per la programmazione funzionale che ho citato nella mia risposta di seguito è buono.) Nessuno dei miliardi di tutorial per analogia si avvicina.
ShreevatsaR,

13
Questa traduzione JavaScript del post di Sigfpe è il nuovo modo migliore per imparare le monadi, per le persone che non hanno già sviluppato Haskell avanzato!
Sam Watkins,

1
È così che ho imparato cos'è una monade. Guidare il lettore attraverso il processo di inventare un concetto è spesso il modo migliore per insegnare il concetto.
Giordania,

Tuttavia, una funzione che accetta l'oggetto schermo come argomento e restituisce la sua copia con il testo modificato sarebbe pura.
Dmitri Zaitsev,

87

Una monade è un tipo di dati che ha due operazioni: >>=(aka bind) e return(aka unit). returnprende un valore arbitrario e crea un'istanza della monade con essa. >>=prende un'istanza della monade e mappa una funzione su di essa. (Si può già vedere che una monade è uno strano tipo di tipo di dati, poiché nella maggior parte dei linguaggi di programmazione non è possibile scrivere una funzione che assume un valore arbitrario e ne crea un tipo. Le monadi usano una sorta di polimorfismo parametrico .)

Nella notazione di Haskell, viene scritta l'interfaccia della monade

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Queste operazioni dovrebbero obbedire a certe "leggi", ma ciò non è terribilmente importante: le "leggi" codificano semplicemente il modo in cui le azioni sensate delle operazioni dovrebbero comportarsi (in sostanza, >>=e returndovrebbero essere d'accordo su come i valori si trasformano in istanze di monade e che >>=è associativo).

Le monadi non riguardano solo lo stato e l'I / O: astraggono un modello comune di calcolo che include il lavoro con stato, I / O, eccezioni e non determinismo. Probabilmente le monadi più semplici da comprendere sono elenchi e tipi di opzioni:

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

dove []e :sono i costruttori di elenchi, ++è l'operatore di concatenazione Juste Nothingsono i Maybecostruttori. Entrambe queste monadi incapsulano modelli di calcolo comuni e utili sui rispettivi tipi di dati (si noti che nessuno dei due ha a che fare con effetti collaterali o I / O).

Devi davvero giocare a scrivere un codice Haskell non banale per apprezzare di cosa parlano le monadi e perché sono utili.


Cosa intendi esattamente con "mappa una funzione su di essa"?
Casebash

Casebash, sono stato deliberatamente informale nell'introduzione. Vedi gli esempi verso la fine per avere un'idea di cosa comporta la "mappatura di una funzione".
Chris Conway,

3
Monad non è un tipo di dati. È una regola di composizione delle funzioni: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev ha ragione, Monads in realtà fornisce il proprio tipo di dati, Monads arent tipi di dati
sksallaj

78

Dovresti prima capire cos'è un funzione. Prima di ciò, capire le funzioni di ordine superiore.

Una funzione di ordine superiore è semplicemente una funzione che accetta una funzione come argomento.

Un funtore è un qualsiasi tipo di costruzione Tper i quali esiste una funzione di ordine superiore, chiamare map, che trasforma una funzione di tipo a -> b(dati due tipi ae b) in una funzione T a -> T b. Questa mapfunzione deve anche obbedire alle leggi dell'identità e della composizione in modo tale che le seguenti espressioni siano vere per tutti pe q(notazione di Haskell):

map id = id
map (p . q) = map p . map q

Ad esempio, un costruttore di tipi chiamato Listè un funzione se è dotato di una funzione di tipo (a -> b) -> List a -> List bche obbedisce alle leggi sopra. L'unica implementazione pratica è ovvia. La List a -> List bfunzione risultante scorre sull'elenco indicato, chiamando la (a -> b)funzione per ciascun elemento e restituisce l'elenco dei risultati.

Una monade è essenzialmente un funtore Tcon due metodi aggiuntivi, join, di tipo T (T a) -> T ae unit(talvolta chiamato return, forko pure) di tipo a -> T a. Per gli elenchi in Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Perché è utile? Perché è possibile, ad esempio, mapsu un elenco con una funzione che restituisce un elenco. Joinprende l'elenco risultante di elenchi e li concatena. Listè una monade perché questo è possibile.

È possibile scrivere una funzione che lo fa map, quindi join. Questa funzione è chiamata bind, o flatMap, oppure (>>=), o (=<<). Questo è normalmente il modo in cui viene fornita un'istanza di monade in Haskell.

Una monade deve soddisfare determinate leggi, vale a dire che joindeve essere associativa. Ciò significa che se hai un valore xdi tipo [[[a]]]allora join (join x)dovrebbe essere uguale join (map join x). E puredeve essere un'identità per jointale join (pure x) == x.


3
leggera aggiunta alla definizione di "funzione di ordine superiore": possono accettare le funzioni OR RITORNO. Ecco perché sono "più alti" perché fanno le cose con se stessi.
Kevin ha vinto il

9
Secondo tale definizione, l'addizione è una funzione di ordine superiore. Prende un numero e restituisce una funzione che aggiunge quel numero a un altro. Quindi no, le funzioni di ordine superiore sono rigorosamente funzioni il cui dominio è costituito da funzioni.
Apocalisp

Il video " Brian Beckman: Don't fear the Monad " segue questa stessa linea di logica.
icc97,

48

[Dichiarazione di non responsabilità: sto ancora cercando di ingannare completamente le monadi. Quello che segue è proprio quello che ho capito finora. Se è sbagliato, spero che qualcuno esperto mi chiamerà sul tappeto.]

Arnar ha scritto:

Le monadi sono semplicemente un modo per avvolgere le cose e forniscono metodi per fare operazioni sulle cose avvolte senza scartarle.

È proprio così. L'idea è questa:

  1. Prendi un qualche tipo di valore e lo avvolgi con alcune informazioni aggiuntive. Proprio come il valore è di un certo tipo (es. Un numero intero o una stringa), quindi le informazioni aggiuntive sono di un certo tipo.

    Ad esempio, tali informazioni extra potrebbero essere a Maybeo a IO.

  2. Quindi hai alcuni operatori che ti consentono di operare sui dati spostati mentre porti con te quelle informazioni aggiuntive. Questi operatori utilizzano le informazioni aggiuntive per decidere come modificare il comportamento dell'operazione sul valore di wrapping.

    Ad esempio, a Maybe Intpuò essere un Just Into Nothing. Ora, se aggiungi a Maybe Inta Maybe Int, l'operatore verificherà se entrambi sono Just Intdentro e, in tal caso, scarterà Inti messaggi, li passerà all'operatore addizione, avvolgendo nuovamente il risultante Intin un nuovo Just Int(che è valido Maybe Int) e quindi restituire a Maybe Int. Ma se uno di loro fosse Nothingdentro, questo operatore tornerà immediatamente Nothing, il che è di nuovo valido Maybe Int. In questo modo, puoi far finta che i tuoi Maybe Intsono solo numeri normali ed eseguire regolarmente la matematica su di essi. Se dovessi ottenere un Nothing, le tue equazioni produrranno comunque il risultato giusto, senza che tu debba controllare i rifiuti Nothingovunque .

Ma l'esempio è proprio quello che succede Maybe. Se le informazioni extra fossero an IO, allora quell'operatore speciale definito per IOs sarebbe invece chiamato e potrebbe fare qualcosa di completamente diverso prima di eseguire l'aggiunta. (OK, aggiungere due IO Ints insieme è probabilmente senza senso - non ne sono ancora sicuro.) (Inoltre, se hai prestato attenzione Maybeall'esempio, hai notato che "avvolgere un valore con cose extra" non è sempre corretto. Ma è difficile per essere esatti, corretti e precisi senza essere imperscrutabili.)

Fondamentalmente, "monade" significa approssimativamente "modello" . Ma invece di un libro pieno di Pattern spiegati in modo informale e specificatamente chiamati, ora hai un costrutto linguistico - sintassi e tutto - che ti permette di dichiarare nuovi schemi come cose nel tuo programma . (L'imprecisione qui è che tutti i modelli devono seguire una forma particolare, quindi una monade non è così generica come un modello. Ma penso che sia il termine più vicino che la maggior parte delle persone conosce e comprende.)

Ed è per questo che la gente trova le monadi così confuse: perché sono un concetto così generico. Chiedere cosa rende qualcosa una monade è altrettanto vago come chiedere cosa rende qualcosa uno schema.

Ma pensa alle implicazioni di avere un supporto sintattico nel linguaggio per l'idea di un modello: invece di dover leggere il libro Gang of Four e memorizzare la costruzione di un modello particolare, devi solo scrivere un codice che implementa questo modello in modo agnostico, modo generico una volta e poi hai finito! Puoi quindi riutilizzare questo modello, come Visitatore o Strategia o Facciata o altro, semplicemente decorando con esso le operazioni nel tuo codice, senza doverlo implementare ancora e ancora!

Questo è il motivo per cui le persone che capiscono le monadi le trovano così utili : non è un concetto di torre d'avorio che gli snob intellettuali sono orgogliosi della comprensione (OK, anche questo ovviamente, teehee), ma in realtà semplifica il codice.


12
A volte una spiegazione di uno "studente" (come te) è più rilevante per un altro studente di una spiegazione proveniente da un esperto. Gli studenti pensano allo stesso modo :)
Adrian

Ciò che rende qualcosa una monade è l'esistenza di una funzione con il tipo M (M a) -> M a. Il fatto che tu possa trasformarlo in uno di tipo M a -> (a -> M b) -> M bè ciò che li rende utili.
Jeremy List,

"monade" significa approssimativamente "modello" ... no.
Grazie,

44

Dopo molti sforzi, penso di aver finalmente capito la monade. Dopo aver riletto la mia lunga critica alla risposta in modo schiacciante al massimo dei voti, offrirò questa spiegazione.

Ci sono tre domande a cui bisogna rispondere per capire le monadi:

  1. Perché hai bisogno di una monade?
  2. Cos'è una monade?
  3. Come viene implementata una monade?

Come ho notato nei miei commenti originali, troppe spiegazioni della monade vengono catturate nella domanda numero 3, senza, e prima di coprire davvero adeguatamente la domanda 2 o la domanda 1.

Perché hai bisogno di una monade?

I linguaggi funzionali puri come Haskell sono diversi dai linguaggi imperativi come C o Java in quanto un programma funzionale puro non è necessariamente eseguito in un ordine specifico, un passo alla volta. Un programma Haskell è più simile a una funzione matematica, in cui è possibile risolvere l '"equazione" in un numero qualsiasi di potenziali ordini. Ciò conferisce una serie di vantaggi, tra cui l'eliminazione della possibilità di alcuni tipi di bug, in particolare quelli relativi a cose come "stato".

Tuttavia, ci sono alcuni problemi che non sono così semplici da risolvere con questo stile di programmazione. Alcune cose, come la programmazione della console e l'I / O dei file, richiedono che le cose accadano in un ordine particolare o devono mantenere lo stato. Un modo per affrontare questo problema è creare un tipo di oggetto che rappresenta lo stato di un calcolo e una serie di funzioni che accettano un oggetto stato come input e restituiscono un nuovo oggetto stato modificato.

Quindi creiamo un ipotetico valore di "stato", che rappresenta lo stato di una schermata della console. esattamente come viene costruito questo valore non è importante, ma diciamo che è una matrice di caratteri ASCII di lunghezza byte che rappresenta ciò che è attualmente visibile sullo schermo e una matrice che rappresenta l'ultima riga di input immessa dall'utente, in pseudocodice. Abbiamo definito alcune funzioni che prendono lo stato della console, lo modificano e restituiscono un nuovo stato della console.

consolestate MyConsole = new consolestate;

Quindi, per fare la programmazione da console, ma in modo puramente funzionale, dovresti annidare molte chiamate di funzioni all'interno di ognuna.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

La programmazione in questo modo mantiene lo stile funzionale "puro", forzando nel contempo le modifiche alla console in un ordine particolare. Ma probabilmente vorremmo fare più di poche operazioni alla volta come nell'esempio sopra. Le funzioni di nidificazione in questo modo inizieranno a diventare sgraziate. Quello che vogliamo è un codice che fa essenzialmente la stessa cosa di cui sopra, ma è scritto un po 'più in questo modo:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Questo sarebbe davvero un modo più conveniente per scriverlo. Come possiamo farlo?

Cos'è una monade?

Una volta che hai un tipo (come consolestate) che definisci insieme a un gruppo di funzioni progettate specificamente per operare su quel tipo, puoi trasformare l'intero pacchetto di queste cose in una "monade" definendo un operatore come :(bind) che automaticamente alimenta i valori di ritorno a sinistra, in parametri di funzione a destra e un liftoperatore che trasforma le normali funzioni, in funzioni che funzionano con quel tipo specifico di operatore di bind.

Come viene implementata una monade?

Vedi altre risposte, che sembrano abbastanza libere di entrare nei dettagli.


Il sequenziamento non è l'unico motivo per definire una monade. Una monade è qualsiasi funzione che ha vincolo e ritorno. Bind e return ti danno il sequenziamento. Ma danno anche altre cose. Inoltre, nota che la tua lingua imperativa preferita è effettivamente una monade IO fantasiosa con classi OO. Rendere facile la definizione delle monadi significa che è facile usare il modello dell'interprete: definire una dsl come monade e interpretarla!
nomen


38

Dopo aver dato una risposta a questa domanda qualche anno fa, credo di poter migliorare e semplificare quella risposta con ...

Una monade è una tecnica di composizione di funzioni che esternalizza il trattamento per alcuni scenari di input usando una funzione di composizione bind, per pre-elaborare l'input durante la composizione.

Nella composizione normale, la funzione compose (>>)è utilizzata per applicare la funzione composta al risultato del suo predecessore in sequenza. È importante sottolineare che la funzione composta è necessaria per gestire tutti gli scenari del suo input.

(x -> y) >> (y -> z)

Questo progetto può essere migliorato ristrutturando l'input in modo che gli stati rilevanti possano essere interrogati più facilmente. Quindi, anziché semplicemente yil valore può diventare Mbtale, ad esempio, (is_OK, b)se yincluso un concetto di validità.

Ad esempio, quando l'ingresso è soltanto possibile un numero, invece di restituire una stringa che può contenere diligentemente contenere un numero o no, si potrebbe ristrutturare il tipo in un boolindicante la presenza di un numero valido e un numero tupla come, bool * float. Le funzioni composte non dovrebbero più analizzare una stringa di input per determinare se esiste un numero, ma potrebbero semplicemente ispezionare la boolporzione di una tupla.

(Ma -> Mb) >> (Mb -> Mc)

Anche in questo caso la composizione avviene naturalmente con composee quindi ogni funzione deve gestire tutti gli scenari del suo input singolarmente, sebbene farlo ora sia molto più semplice.

Tuttavia, se potessimo esternare lo sforzo dell'interrogatorio per quei tempi in cui la gestione di uno scenario è di routine. Ad esempio, cosa succede se il nostro programma non fa nulla quando l'ingresso non è OK come in quando lo is_OKè false. Se ciò avvenisse, le funzioni composte non avrebbero bisogno di gestire da sole quello scenario, semplificando notevolmente il loro codice ed effettuando un altro livello di riutilizzo.

Per raggiungere questa esternalizzazione potremmo usare una funzione bind (>>=), per eseguire compositioninvece di compose. Come tale, invece di trasferire semplicemente i valori dall'output di una funzione all'input di un'altra Bind, ispezionerebbe la Mporzione di Mae deciderebbe se e come applicare la funzione composta a a. Naturalmente, la funzione bindsarebbe definita specificamente per i nostri particolari in Mmodo da poterne ispezionare la struttura ed eseguire qualunque tipo di applicazione desideriamo. Nondimeno, apuò essere qualsiasi cosa poiché si bindlimita a passare il non aatteso alla funzione composta quando determina l'applicazione necessaria. Inoltre, le funzioni composte stesse non devono più occuparsi diMparte della struttura di input, semplificandoli. Quindi...

(a -> Mb) >>= (b -> Mc) o più succintamente Mb >>= (b -> Mc)

In breve, una monade esternalizza e quindi fornisce un comportamento standard attorno al trattamento di determinati scenari di input una volta che l'input viene progettato per esporli sufficientemente. Questo design è un shell and contentmodello in cui la shell contiene dati rilevanti per l'applicazione della funzione composta ed è interrogata da e rimane disponibile solo per la bindfunzione.

Pertanto, una monade è tre cose:

  1. un Mguscio per contenere informazioni rilevanti sulla monade,
  2. una bindfunzione implementata per utilizzare queste informazioni della shell nella sua applicazione delle funzioni composte ai valori del contenuto che trova all'interno della shell, e
  3. funzioni compostabili del modulo, che a -> Mbproducono risultati che includono dati di gestione monadici.

In generale, l'input per una funzione è molto più restrittivo del suo output che può includere elementi come le condizioni di errore; quindi, la Mbstruttura dei risultati è generalmente molto utile. Ad esempio, l'operatore di divisione non restituisce un numero quando lo è il divisore 0.

Inoltre, monads può includere funzioni di avvolgimento che avvolgono valori, anel tipo monadico Mae funzioni generali a -> b, in funzioni monadiche a -> Mb, avvolgendo i loro risultati dopo l'applicazione. Naturalmente, come bind, tali funzioni di avvolgimento sono specifiche di M. Un esempio:

let return a = [a]
let lift f a = return (f a)

Il design della bindfunzione presuppone strutture dati immutabili e funzioni pure, altre cose diventano complesse e non si possono fare garanzie. Come tale, ci sono leggi monadiche:

Dato...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Poi...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitysignifica che bindmantiene l'ordine di valutazione indipendentemente da quando bindviene applicato. Cioè, nella definizione di cui Associativitysopra, la forza di valutazione precoce della parentesi bindingdi fe gsi tradurrà solo in una funzione che si aspetta Maal fine di completare il bind. Quindi la valutazione di Madeve essere determinata prima che il suo valore possa essere applicato fe il risultato a sua volta applicato g.


"... ma spero che altri lo trovino utile" è stato davvero utile per me, nonostante tutte le frasi enfatizzate: D

Questa è la spiegazione più concisa e chiara delle monadi che abbia mai letto / visto / ascoltato. Grazie!
James,

C'è una differenza importante tra Monade e Monoid. La monade è una regola per "comporre" funzioni tra tipi diversi , quindi non formano un'operazione binaria come richiesto per i Monoidi, vedere qui per maggiori dettagli: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Sì. Hai ragione. Il tuo articolo era sopra la mia testa :). Tuttavia, ho trovato questo trattamento molto utile (e l'ho aggiunto al mio come una direzione per gli altri). Grazie il tuo avvertimento
George

2
Potresti aver confuso la teoria dei gruppi algebrici con la teoria della Categoria da cui proviene Monad. La prima è la teoria dei gruppi algebrici, che non è correlata.
Dmitri Zaitsev

37

Una monade è, in effetti, una forma di "operatore di tipo". Farà tre cose. Innanzitutto "avvolgerà" (o convertirà altrimenti) un valore di un tipo in un altro tipo (in genere chiamato "tipo monadico"). In secondo luogo renderà disponibili tutte le operazioni (o funzioni) sul tipo sottostante sul tipo monadico. Infine fornirà supporto per combinare se stesso con un'altra monade per produrre una monade composita.

La "forse monade" è essenzialmente l'equivalente di "tipi nullable" in Visual Basic / C #. Prende un tipo "N" non nullable e lo converte in un "Nullable <T>", quindi definisce il significato di tutti gli operatori binari in un Nullable <T>.

Gli effetti collaterali sono rappresentati in modo similare. Viene creata una struttura che contiene descrizioni degli effetti collaterali insieme al valore restituito di una funzione. Le operazioni "sollevate" quindi copiano attorno agli effetti collaterali quando i valori vengono passati tra le funzioni.

Sono chiamati "monadi" piuttosto che il nome più facile da comprendere di "operatori di tipo" per diversi motivi:

  1. Le monadi hanno restrizioni su ciò che possono fare (vedere la definizione per i dettagli).
  2. Tali restrizioni, insieme al fatto che sono coinvolte tre operazioni, si conformano alla struttura di qualcosa chiamata monade nella teoria delle categorie, che è un ramo oscuro della matematica.
  3. Sono stati progettati da sostenitori di linguaggi funzionali "puri"
  4. Sostenitori di linguaggi funzionali puri come oscuri rami della matematica
  5. Poiché la matematica è oscura e le monadi sono associate a particolari stili di programmazione, le persone tendono ad usare la parola monade come una sorta di stretta di mano segreta. Per questo motivo nessuno si è preso la briga di investire in un nome migliore.

1
Le monadi non sono state "progettate", ma sono state applicate da un dominio (teoria delle categorie) a un altro (I / O in linguaggi di programmazione puramente funzionali). Newton ha "progettato" il calcolo?
Jared Updike,

1
I precedenti punti 1 e 2 sono corretti e utili. I punti 4 e 5 sono una sorta di ominem, anche se più o meno veri. Non aiutano davvero a spiegare le monadi.
Jared Updike,

13
Ri: 4, 5: La cosa della "stretta di mano segreta" è un'aringa rossa. La programmazione è piena di gergo. A Haskell capita di chiamare roba così com'è senza pretendere di riscoprire qualcosa. Se esiste già in matematica, perché crearne un nuovo nome? Il nome non è in realtà il motivo per cui le persone non prendono le monadi; sono un concetto sottile. La persona media probabilmente comprende addizione e moltiplicazione, perché non ottengono il concetto di un gruppo abeliano? Perché è più astratto e generale e quella persona non ha fatto il lavoro per avvolgere la testa attorno al concetto. Un cambio di nome non sarebbe d'aiuto.
Jared Updike,

16
Sospiro ... Non sto attaccando Haskell ... Stavo scherzando. Quindi, non mi interessa davvero essere "ad hominem". Sì, il calcolo è stato "progettato". Ecco perché, ad esempio, agli studenti di calcolo viene insegnata la notazione di Leibniz, piuttosto che l'enorme roba usata da Netwton. Migliore design. I buoni nomi aiutano a capire molto. Se avessi chiamato i gruppi abeliani "baccelli distesi per le rughe", potresti avere difficoltà a capirmi. Potresti dire "ma quel nome non ha senso", nessuno li chiamerebbe mai così. Per le persone che non hanno mai sentito parlare della teoria delle categorie "monade" sembra una sciocchezza.
Scott Wisniewski,

4
@Scott: scusate se i miei ampi commenti mi hanno fatto sembrare che stavo diventando difensivo su Haskell. Mi piace il tuo umorismo per la stretta di mano segreta e noterai che ho detto che è più o meno vero. :-) Se avessi chiamato i gruppi abeliani "baccelli distesi per le rughe", avresti commesso lo stesso errore nel cercare di dare alle monadi un "nome migliore" (cfr. F # "espressioni di calcolo"): il termine esiste e le persone a cui importa sapere quali monadi sono, ma non quali sono le "cose ​​sfocate calde" (o le "espressioni di calcolo"). Se capisco correttamente l'uso del termine "operatore di tipo", ci sono molti altri operatori di tipo oltre alle monadi.
Jared Updike,

35

(Vedi anche le risposte in Cos'è una monade? )

Una buona motivazione per Monads è che potresti aver inventato Monads di Sigfpe (Dan Piponi) ! (E forse hai già) . Ci sono MOLTE altre esercitazioni sulle monadi , molte delle quali cercano erroneamente di spiegare le monadi in "termini semplici" usando varie analogie: questo è l' errore della monade ; evitarli.

Come dice DR MacIver in Dicci perché la tua lingua fa schifo :

Quindi, le cose che odio di Haskell:

Cominciamo con l'ovvio. Tutorial Monade. No, non monadi. In particolare i tutorial. Sono infiniti, esagerati e caro dio sono noiosi. Inoltre, non ho mai visto prove convincenti che effettivamente aiutino. Leggi la definizione della classe, scrivi un po 'di codice, supera il nome spaventoso.

Dici di aver capito forse la monade? Bene, stai arrivando. Inizia ad usare altre monadi e prima o poi capirai quali sono le monadi in generale.

[Se sei orientato matematicamente, potresti voler ignorare le dozzine di tutorial e apprendere la definizione o seguire le lezioni nella teoria delle categorie :) La parte principale della definizione è che una Monade M coinvolge un "costruttore di tipi" che definisce per ogni tipo esistente "T" un nuovo tipo "MT" e alcuni modi per andare avanti e indietro tra tipi "normali" e tipi "M".]

Inoltre, sorprendentemente, una delle migliori introduzioni alle monadi è in realtà uno dei primi documenti accademici che introducono le monadi, le Monadi di Philip Wadler per la programmazione funzionale . In realtà ha esempi motivanti pratici, non banali , a differenza di molti tutorial artificiali là fuori.


2
L'unico problema con il documento di Wadler è che la notazione è diversa, ma concordo sul fatto che il documento sia piuttosto convincente e una chiara motivazione concisa per l'applicazione delle monadi.
Jared Updike,

+1 per la "fallacia del tutorial della monade". I tutorial sulle monadi sono simili a quelli di molti tutorial che cercano di spiegare il concetto di numeri interi. Un tutorial direbbe "1 è simile a una mela"; un altro tutorial dice "2 è come una pera"; un terzo dice "3 è fondamentalmente un'arancia". Ma non ottieni mai l'intera immagine da un singolo tutorial. Ciò che ho preso è che le monadi sono un concetto astratto che può essere utilizzato per molti scopi abbastanza diversi.
stakx - non contribuisce più al

@stakx: Sì, vero. Ma non intendevo dire che le monadi sono un'astrazione che non puoi imparare o che non dovresti imparare; solo che è meglio impararlo dopo aver visto abbastanza esempi concreti da percepire una singola astrazione sottostante. Vedi la mia altra risposta qui .
ShreevatsaR,

5
A volte sento che ci sono così tanti tutorial che cercano di convincere il lettore che le monadi sono utili usando il codice che fa cose complicate o utili. Ciò ha ostacolato la mia comprensione per mesi. Non imparo così. Preferisco vedere un codice estremamente semplice, facendo qualcosa di stupido che posso mentalmente superare e non sono riuscito a trovare questo tipo di esempio. Non riesco a capire se il primo esempio è una monade per analizzare una grammatica complicata. Posso imparare se è una monade per sommare gli interi.
Rafael S. Calsaverini,

Il solo riferimento al tipo di costruttore è incompleto: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Le monadi controllano il flusso di quali tipi di dati astratti sono dati.

In altre parole, molti sviluppatori sono a proprio agio con l'idea di Set, Liste, Dizionari (o Hash o Mappe) e Alberi. All'interno di questi tipi di dati ci sono molti casi speciali (ad esempio InsertionOrderPreservingIdentityHashMap).

Tuttavia, di fronte al "flusso" del programma, molti sviluppatori non sono stati esposti a molti più costrutti di se, switch / case, do, while, goto (grr), e (forse) chiusure.

Quindi, una monade è semplicemente un costrutto di flusso di controllo. Una frase migliore per sostituire la monade sarebbe "tipo di controllo".

Come tale, una monade ha slot per la logica di controllo, o istruzioni o funzioni - l'equivalente nelle strutture di dati sarebbe quello di dire che alcune strutture di dati consentono di aggiungere dati e rimuoverli.

Ad esempio, la monade "if":

if( clause ) then block

nella sua forma più semplice ha due slot: una clausola e un blocco. La ifmonade è di solito costruita per valutare il risultato della clausola e, se non falso, valutare il blocco. Molti sviluppatori non vengono introdotti alle monadi quando imparano "se", e non è necessario comprendere le monadi per scrivere una logica efficace.

Le monadi possono diventare più complicate, allo stesso modo in cui le strutture di dati possono diventare più complicate, ma ci sono molte grandi categorie di monade che possono avere semantica simile, ma implementazioni e sintassi diverse.

Naturalmente, allo stesso modo in cui le strutture di dati possono essere ripetute o attraversate, le monadi possono essere valutate.

I compilatori possono avere o meno supporto per le monadi definite dall'utente. Haskell lo fa certamente. Ioke ha alcune capacità simili, sebbene il termine monade non sia usato nella lingua.


14

Il mio tutorial Monad preferito:

http://www.haskell.org/haskellwiki/All_About_Monads

(su 170.000 risultati di una ricerca su Google "tutorial monade"!)

@Stu: il punto delle monadi è di permetterti di aggiungere (solitamente) semantica sequenziale a codice altrimenti puro; puoi persino comporre monadi (usando Monad Transformers) e ottenere semantiche combinate più interessanti e complicate, come ad esempio l'analisi con la gestione degli errori, lo stato condiviso e la registrazione. Tutto questo è possibile in puro codice, le monadi ti permettono solo di sottrarlo e riutilizzarlo in librerie modulari (sempre buone nella programmazione), oltre a fornire una sintassi conveniente per renderlo imperativo.

Haskell ha già un sovraccarico da parte dell'operatore [1]: utilizza le classi di tipo in modo molto simile a come si potrebbero usare le interfacce in Java o C #, ma Haskell permette anche ai token non alfanumerici come + && e> come identificatori di infissi. È solo un sovraccarico dell'operatore nel tuo modo di vederlo se intendi "sovraccarico del punto e virgola" [2]. Sembra una magia nera e chiedere problemi per "sovraccaricare il punto e virgola" (gli intraprendenti hacker di Perl che ottengono l'idea di questa idea) ma il punto è che senza monadi non c'è punto e virgola, poiché il codice puramente funzionale non richiede o consente il sequenziamento esplicito.

Tutto ciò suona molto più complicato del necessario. L'articolo di sigfpe è piuttosto interessante, ma usa Haskell per spiegarlo, che in qualche modo non riesce a rompere il problema del pollo e delle uova nel comprendere Haskell per ingannare Monadi e capire Monadi per ingannare Haskell.

[1] Questo è un problema separato dalle monadi ma le monadi usano la funzione di sovraccarico dell'operatore di Haskell.

[2] Anche questa è una semplificazione eccessiva poiché l'operatore per il concatenamento delle azioni monadiche è >> = (pronunciato "bind") ma c'è zucchero sintattico ("do") che ti consente di usare parentesi graffe e punti e virgola e / o rientri e newline.


9

Ultimamente ho pensato alle Monadi in un modo diverso. Li ho pensati come astrarre l' ordine di esecuzione in modo matematico, il che rende possibili nuovi tipi di polimorfismo.

Se stai usando un linguaggio imperativo e scrivi alcune espressioni in ordine, il codice SEMPRE gira esattamente in quell'ordine.

E nel caso semplice, quando si utilizza una monade, si sente lo stesso: si definisce un elenco di espressioni che avvengono in ordine. Solo che, a seconda della monade che usi, il tuo codice potrebbe essere eseguito in ordine (come nella monade IO), parallelamente su più elementi contemporaneamente (come nella monade Elenco), potrebbe fermarsi a metà (come nella monade Maybe) , potrebbe interrompersi a metà per essere ripreso in seguito (come in una monade di ripresa), potrebbe riavvolgere e ricominciare dall'inizio (come in una monade di Transazione), oppure potrebbe riavvolgere a metà strada per provare altre opzioni (come in una monade di Logica) .

E poiché le monadi sono polimorfiche, è possibile eseguire lo stesso codice in monadi diverse, a seconda delle esigenze.

Inoltre, in alcuni casi, è possibile combinare le monadi insieme (con i trasformatori di monade) per ottenere più funzioni contemporaneamente.


9

Sono ancora una novità per le monadi, ma ho pensato di condividere un link che ho trovato davvero bello da leggere (CON IMMAGINI !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman / (nessuna affiliazione)

Fondamentalmente, il concetto caldo e sfocato che ho ottenuto dall'articolo era il concetto che le monadi sono fondamentalmente adattatori che consentono a funzioni disparate di lavorare in modo compostabile, cioè essere in grado di mettere insieme più funzioni e mescolarle e abbinarle senza preoccuparsi di un ritorno incoerente tipi e simili. Quindi la funzione BIND è incaricata di mantenere le mele con le mele e le arance con le arance quando stiamo cercando di realizzare questi adattatori. E la funzione LIFT ha il compito di prendere le funzioni di "livello inferiore" e di "aggiornarle" per lavorare con le funzioni BIND ed essere anche compostabili.

Spero di aver capito bene e, cosa più importante, spero che l'articolo abbia una visione valida delle monadi. Se non altro, questo articolo ha contribuito a stimolare il mio appetito per saperne di più sulle monadi.


Gli esempi di Python hanno reso facile la comprensione! Grazie per la condivisione.
Ryan Efendy,

8

Oltre alle eccellenti risposte di cui sopra, lascia che ti offra un link al seguente articolo (di Patrick Thomson) che spiega le monadi mettendo in relazione il concetto con la libreria JavaScript jQuery (e il suo modo di usare il "concatenamento di metodi" per manipolare il DOM) : jQuery è una monade

La stessa documentazione di jQuery non si riferisce al termine "monade" ma parla del "modello di costruttore" che è probabilmente più familiare. Questo non cambia il fatto che tu abbia una monade propria lì forse senza nemmeno accorgersene.


Se usi jQuery, questa spiegazione può essere molto utile, specialmente se il tuo Haskell non è forte
byteclub

10
JQuery non è decisamente una monade. L'articolo collegato è sbagliato.
Tony Morris,

1
Essere "enfatici" non è molto convincente. Per qualche utile discussione sull'argomento, vedi jQuery è una monade
StackTranslate.it

1
Vedi anche Google Talk Monads e Gonads di Douglas Crackford e il suo codice Javascript per fare i modad, espandendo il comportamento simile delle librerie AJAX e promesse: douglascrockford / monade · GitHub
nealmcb


7

Una monade è un modo per combinare insieme calcoli che condividono un contesto comune. È come costruire una rete di tubi. Quando si costruisce la rete, non vi sono dati che la attraversano. Ma quando ho finito di mettere insieme tutti i bit insieme a 'bind' e 'return', invoco qualcosa di simile runMyMonad monad datae i dati scorrono attraverso le pipe.


1
È più simile a Applicativo che a Monade. Con Monads, devi ottenere i dati dalle pipe prima di poter scegliere la pipe successiva da connettere.
Peaker il

sì, descrivi l'Applicativo, non la Monade. Monade sta costruendo il prossimo segmento di tubo sul posto, a seconda dei dati che hanno raggiunto quel punto, all'interno del tubo.
Will Ness,

6

In pratica, monade è un'implementazione personalizzata dell'operatore di composizione delle funzioni che si occupa degli effetti collaterali e dei valori di input e return incompatibili (per il concatenamento).



5

Le due cose che mi hanno aiutato meglio quando ho saputo che c'erano:

Capitolo 8, "Parser funzionali", dal libro di Graham Hutton Programmazione in Haskell . Questo non menziona affatto le monadi, in realtà, ma se riesci a sfogliare il capitolo e capire veramente tutto in esso, in particolare come viene valutata una sequenza di operazioni di associazione, capirai gli interni delle monadi. Aspettatevi che questo richieda diversi tentativi.

Il tutorial All About Monads . Questo fornisce diversi buoni esempi del loro uso, e devo dire che l'analogia in Appendex mi ha aiutato.


5

Monoid sembra essere qualcosa che garantisce che tutte le operazioni definite su un Monoid e un tipo supportato restituiranno sempre un tipo supportato all'interno del Monoid. Ad esempio, Qualsiasi numero + Qualsiasi numero = Un numero, nessun errore.

Mentre la divisione accetta due frazionari e restituisce un frazionario, che ha definito la divisione per zero come Infinity in haskell somewhy (che risulta essere un frazionario somewhy) ...

In ogni caso, sembra che le Monadi siano solo un modo per garantire che la catena di operazioni si comporti in modo prevedibile e una funzione che afferma di essere Num -> Num, composta con un'altra funzione di Num-> Num chiamata con x non diciamo, lancia i missili.

D'altra parte, se abbiamo una funzione che lancia i missili, possiamo comporla con altre funzioni che sparano anche i missili, perché il nostro intento è chiaro - vogliamo sparare i missili - ma non ci proverà stampa "Hello World" per qualche strana ragione.

In Haskell, main è di tipo IO () o IO [()], la distinzione è strana e non ne discuterò ma ecco cosa penso:

Se ho main, voglio che faccia una catena di azioni, il motivo per cui eseguo il programma è quello di produrre un effetto, di solito tramite IO. In questo modo posso concatenare le operazioni di IO in generale al fine di - fare IO, nient'altro.

Se provo a fare qualcosa che non "restituisce IO", il programma si lamenterà che la catena non scorre, o fondamentalmente "In che modo ciò si collega a ciò che stiamo cercando di fare - un'azione IO", sembra forzare il programmatore deve mantenere il filo del pensiero, senza allontanarsi e pensare di sparare i missili, creando al contempo algoritmi per l'ordinamento - che non scorre.

Fondamentalmente, Monads sembra essere un suggerimento per il compilatore che "hey, conosci questa funzione che restituisce un numero qui, in realtà non sempre funziona, a volte può produrre un Numero, e talvolta Niente affatto, basta tenerlo in mente". Sapendo questo, se provi ad affermare un'azione monadica, l'azione monadica può agire come un'eccezione del tempo di compilazione dicendo "ehi, questo non è in realtà un numero, questo PU be essere un numero, ma non puoi supporre questo, fai qualcosa per garantire che il flusso sia accettabile ". che impedisce il comportamento imprevedibile del programma - in buona misura.

Sembra che le monadi non riguardino la purezza, né il controllo, ma il mantenimento di un'identità di una categoria in cui tutti i comportamenti sono prevedibili e definiti o non vengono compilati. Non puoi fare nulla quando ci si aspetta che tu faccia qualcosa, e non puoi fare qualcosa se non devi fare nulla (visibile).

Il motivo principale che mi viene in mente per Monads è: vai al codice procedurale / OOP, e noterai che non sai dove inizia il programma, né finisce, tutto ciò che vedi è un sacco di salti e molta matematica , magia e missili. Non sarai in grado di mantenerlo, e se puoi, trascorrerai molto tempo avvolgendo la tua mente attorno all'intero programma prima di poterne comprendere qualsiasi parte, perché la modularità in questo contesto si basa su "sezioni" interdipendenti di codice, in cui il codice è ottimizzato per essere il più possibile correlato per promessa di efficienza / inter-relazione. Le monadi sono molto concrete e ben definite per definizione e garantiscono che sia possibile analizzare il flusso del programma e isolare parti che sono difficili da analizzare, in quanto esse stesse sono monadi. Una monade sembra essere un " o distruggere l'universo o addirittura distorcere il tempo - non abbiamo idea né garanzie che sia COSA È. Una monade GARANTISCE CHE È QUELLO CHE È. che è molto potente. o distruggere l'universo o addirittura distorcere il tempo - non abbiamo idea né garanzie che sia COSA È. Una monade GARANTISCE CHE È QUELLO CHE È. che è molto potente.

Tutte le cose nel "mondo reale" sembrano essere monadi, nel senso che sono vincolate da leggi osservabili definite che impediscono la confusione. Questo non significa che dobbiamo imitare tutte le operazioni di questo oggetto per creare classi, invece possiamo semplicemente dire "un quadrato è un quadrato", nient'altro che un quadrato, nemmeno un rettangolo né un cerchio, e "un quadrato ha un'area della lunghezza di una delle sue dimensioni esistenti moltiplicata per se stessa. Non importa quale quadrato tu abbia, se è un quadrato nello spazio 2D, la sua area non può assolutamente essere altro che la sua lunghezza quadrata, è quasi banale da dimostrare. Questo è molto potente perché non abbiamo bisogno di fare affermazioni per assicurarci che il nostro mondo sia così com'è, usiamo solo le implicazioni della realtà per evitare che i nostri programmi cadano fuori strada.

Sono praticamente sicuro di sbagliarmi, ma penso che questo potrebbe aiutare qualcuno là fuori, quindi speriamo che aiuti qualcuno.


5

Nel contesto di Scala troverete quanto segue per essere la definizione più semplice. Fondamentalmente flatMap (o bind) è "associativo" e esiste un'identità.

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
  }
}

Per esempio

// 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)

NOTA A rigor di termini la definizione di Monade nella programmazione funzionale non è la stessa definizione di Monade nella teoria delle categorie , che è definita a turni di mape flatten. Sebbene siano in qualche modo equivalenti in determinate mappature. Questa presentazione è molto buona: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

Questa risposta inizia con un esempio motivante, funziona attraverso l'esempio, deriva un esempio di monade e definisce formalmente "monade".

Considera queste tre funzioni nello pseudocodice:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

faccetta una coppia ordinata del modulo <x, messages>e restituisce una coppia ordinata. Lascia intatto il primo oggetto e aggiunge "called f. "il secondo elemento. Lo stesso con g.

È possibile comporre queste funzioni e ottenere il valore originale, insieme a una stringa che mostra in quale ordine sono state chiamate le funzioni:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Non ti piace il fatto che fe gresponsabile per aggiungendo i propri messaggi di log per le informazioni di registrazione precedente. (Immagina solo per amor di discussione che invece di aggiungere stringhe, feg deve eseguire una logica complicata sul secondo elemento della coppia. Sarebbe un dolore ripetere quella logica complicata in due - o più - funzioni diverse.)

Preferisci scrivere funzioni più semplici:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Ma guarda cosa succede quando le componi:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Il problema è che passare una coppia in una funzione non ti dà quello che vuoi. E se potessi alimentare una coppia in una funzione:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Leggi feed(f, m)come "feed min f". Per alimentare una coppia <x, messages>in una funzione fè quella di passare x dentro f, ottenere <y, message>fuori fe ritorno <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Nota cosa succede quando fai tre cose con le tue funzioni:

Primo: se si avvolge un valore e quindi si inserisce la coppia risultante in una funzione:

  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)

Ciò equivale a passare il valore nella funzione.

Secondo: se dai una coppia a 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>

Ciò non cambia la coppia.

Terzo: se si definisce una funzione che accetta xe si nutre g(x)in f:

h(x) := feed(f, g(x))

e inserire una coppia in esso:

  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>))

Ciò equivale a nutrire la coppia ge nutrire la coppia risultante f.

Hai la maggior parte di una monade. Ora devi solo conoscere i tipi di dati nel tuo programma.

Che tipo di valore è <x, "called f. ">? Bene, questo dipende da quale tipo di valore xè. Se xè di tipo t, allora la tua coppia è un valore di tipo "coppia di te stringa". Chiama quel tipo M t.

Mè un costruttore di tipi: Mda solo non fa riferimento a un tipo, ma si M _riferisce a un tipo una volta riempito lo spazio vuoto con un tipo. An M intè una coppia di un int e una stringa. An M stringè una coppia di una stringa e una stringa. Eccetera.

Congratulazioni, hai creato una monade!

Formalmente, la tua monade è la tupla <M, feed, wrap> .

Una monade è una tupla in <M, feed, wrap>cui:

  • M è un costruttore di tipi.
  • feedaccetta una (funzione che accetta una te restituisce una M u) e una M te restituisce unaM u .
  • wrapprende a ve restituisce un M v.

t, ue vsono tre tipi che possono essere o meno gli stessi. Una monade soddisfa le tre proprietà che hai dimostrato per la tua specifica monade:

  • Inserire un involucro tin una funzione equivale a passare il non imballato nella tfunzione.

    formalmente: feed(f, wrap(x)) = f(x)

  • Nutrire un M tin wrapnon fa nulla alM t .

    formalmente: feed(wrap, m) = m

  • Nutrire un M t(chiamalo m) in una funzione che

    • passa il t dentrog
    • ottiene un M u(chiamalon ) dag
    • si nutre ninf

    equivale a

    • alimentazione ming
    • ottenere ndag
    • alimentando ninf

    Formalmente: feed(h, m) = feed(f, feed(g, m))doveh(x) := feed(f, g(x))

In genere, feedsi chiama bind(AKA >>=in Haskell) e wrapsi chiama return.


5

Proverò a spiegare Monadnel contesto di Haskell.

Nella programmazione funzionale, la composizione delle funzioni è importante. Permette al nostro programma di essere composto da piccole funzioni di facile lettura.

Diciamo che abbiamo due funzioni: g :: Int -> Stringe f :: String -> Bool.

Possiamo fare (f . g) x, che è lo stesso di f (g x), dove xè un Intvalore.

Quando si esegue la composizione / l'applicazione del risultato di una funzione a un'altra, è importante avere i tipi corrispondenti. Nel caso precedente, il tipo di risultato restituito gdeve essere uguale al tipo accettato daf .

Ma a volte i valori sono in contesti e questo rende un po 'meno facile allineare i tipi. (Avere valori nei contesti è molto utile. Ad esempio, il Maybe Inttipo rappresenta un Intvalore che potrebbe non essere presente, il IO Stringtipo rappresenta un Stringvalore presente a causa dell'esecuzione di alcuni effetti collaterali.)

Diciamo che ora abbiamo g1 :: Int -> Maybe Stringe f1 :: String -> Maybe Bool. g1e f1sono molto simili a ge frispettivamente.

Non possiamo fare (f1 . g1) xo f1 (g1 x), dove xè un Intvalore. Il tipo di risultato restituito da g1non è quello che si f1aspetta.

Potremmo comporre fe gcon l' .operatore, ma ora non possiamo comporre f1e g1con .. Il problema è che non possiamo passare direttamente un valore in un contesto a una funzione che prevede un valore che non si trova in un contesto.

Non sarebbe bello se presentiamo un operatore per comporre g1e f1, in modo tale che possiamo scrivere (f1 OPERATOR g1) x? g1restituisce un valore in un contesto. Il valore verrà rimosso dal contesto e applicato a f1. E sì, abbiamo un tale operatore. Lo è <=<.

Abbiamo anche il >>= operatore che fa esattamente la stessa cosa per noi, anche se con una sintassi leggermente diversa.

Scriviamo: g1 x >>= f1. g1 xè un Maybe Intvalore. L' >>=operatore aiuta a estrarre quel Intvalore dal contesto "forse-non-lì" e ad applicarlo f1. Il risultato di f1, che è a Maybe Bool, sarà il risultato dell'intera >>=operazione.

E infine, perché è Monadutile? Perché Monadè la classe di tipo che definisce l' >>=operatore, molto simile alla Eqclasse di tipo che definisce gli operatori ==e /=.

Per concludere, la Monadclasse di tipo definisce l' >>=operatore che ci consente di passare valori in un contesto (chiamiamo questi valori monadici) a funzioni che non prevedono valori in un contesto. Il contesto sarà curato.

Se c'è una cosa da ricordare qui, è che Monadconsente la composizione di funzioni che coinvolge valori in contesti .



IOW, Monad è un protocollo di chiamata di funzione generalizzato.
Will Ness,

La tua risposta è la più utile secondo me. Anche se devo dire che penso che l'accento debba essere posto sul fatto che le funzioni a cui ti riferisci non coinvolgono solo valori in contesti, ma inseriscono attivamente valori in contesti. Quindi, ad esempio, una funzione, f :: ma -> mb si comporre molto facilmente con un'altra funzione, g :: mb -> m c. Ma le monadi (si legano in modo specifico) ci permettono di comporre perpetuamente funzioni che mettono il loro input nello stesso contesto, senza che noi abbiamo bisogno di togliere prima il valore da quel contesto (che rimuoverebbe effettivamente le informazioni dal valore)
James

@James Penso che dovrebbe essere l'enfasi per i funzioni?
Jonas

@Jonas Immagino di non aver spiegato con disinvoltura. Quando dico che le funzioni inseriscono valori in contesti, intendo che hanno tipo (a -> mb). Questi sono molto utili dal momento che l'inserimento di un valore in un contesto aggiunge nuove informazioni ad esso, ma di solito sarebbe difficile concatenare a (a -> mb) e a (b -> mc) poiché non possiamo semplicemente estrarre il valore del contesto. Quindi dovremmo usare un processo contorto per concatenare queste funzioni in modo sensato a seconda del contesto specifico e le monadi ci permettono di farlo in modo coerente, indipendentemente dal contesto.
James,

5

tl; dr

{-# 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

Prologo

L'operatore dell'applicazione $delle funzioni

forall a b. a -> b

è canonicamente definito

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

in termini di applicazione della funzione primitiva di Haskell f x(infixl 10 ).

La composizione .è definita in termini di $come

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

e soddisfa le equivalenze 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

. è associativo e id è la sua identità destra e sinistra.

La tripla Kleisli

Nella programmazione, una monade è un costruttore di tipo functor con un'istanza della classe di tipo monade. Esistono diverse varianti equivalenti di definizione e implementazione, ognuna con intuizioni leggermente diverse sull'astrazione della monade.

Un functor è un costruttore fdi tipo di tipo * -> *con un'istanza della classe di tipo functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Oltre a seguire il protocollo di tipo imposto staticamente, le istanze della classe di tipo functor devono obbedire alle leggi algebriche del funzione 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

I calcoli di Functor hanno il tipo

forall f t. Functor f => f t

Un calcolo c rconsiste in risultati r nel contesto c .

Le funzioni monadiche unarie o le frecce Kleisli hanno il tipo

forall m a b. Functor m => a -> m b

Le frecce di Kleisi sono funzioni che accettano un argomento ae restituiscono un calcolo monadicom b .

Le monadi sono canonicamente definite in termini di tripla di Kleisli forall m. Functor m =>

(m, return, (=<<))

implementato come classe di tipo

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

L' identità Kleisli return è una freccia Kleisli che promuove un valore tnel contesto monadico m. L'applicazione Extension o Kleisli =<< applica una freccia Kleisli a -> m bai risultati di un calcolom a .

La composizione di Kleisli <=< è definita in termini di estensione come

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< compone due frecce Kleisli, applicando la freccia sinistra ai risultati dell'applicazione della freccia destra.

Le istanze della classe del tipo di monade devono obbedire alle leggi della monade , più elegantemente dichiarate in termini di composizione di Kleisli: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

<=<è associativo edreturn è la sua identità destra e sinistra.

Identità

Il tipo di identità

type Id t = t

è la funzione di identità sui tipi

Id :: * -> *

Interpretato come un funzione

   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)

Nel canonico Haskell viene definita la monade dell'identità

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

Opzione

Un tipo di opzione

data Maybe t = Nothing | Just t

codifica un calcolo Maybe tche non produce necessariamente un risultato t, un calcolo che può "fallire". L'opzione monade è definita

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 bviene applicato a un risultato solo se Maybe aproduce un risultato.

newtype Nat = Nat Int

I numeri naturali possono essere codificati come numeri interi maggiori o uguali a zero.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

I numeri naturali non vengono chiusi in sottrazione.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

L'opzione monade copre una forma base di gestione delle eccezioni.

(-? 20) <=< toNat :: Int -> Maybe Nat

Elenco

La monade elenco, sopra il tipo di elenco

data [] t = [] | t : [t]

infixr 5 :

e la sua operazione additiva monoid "append"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

codifica il calcolo non lineare[t] producendo una quantità naturale 0, 1, ...di risultati 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)
   _ =<< []       = []

L'estensione =<<concatena ++tutti gli elenchi [b]risultanti dalle applicazioni f xdi una freccia Kleisli a -> [b]in elementi di [a]un unico elenco di risultati [b].

Lasciate che i divisori propri di un numero intero positivo nessere

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

poi

forall n.  let { f = f <=< divisors } in f n   =   []

Nel definire la classe del tipo monade, anziché l'estensione =<<, lo standard Haskell usa il suo flip, l' operatore di bind>>= .

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

Per semplicità, questa spiegazione usa la gerarchia di classi di tipi

class              Functor f
class Functor m => Monad m

In Haskell, l'attuale gerarchia standard è

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

perché non solo ogni monade è un funzione, ma ogni applicativo è un funzione e ogni monade è anche un applicativo.

Usando la monade elenco, lo pseudocodice imperativo

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

si traduce approssimativamente nel blocco do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

la comprensione della monade equivalente ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

e l'espressione

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

La notazione e la comprensione delle monadi sono zucchero sintattico per le espressioni di legame annidate. L'operatore bind viene utilizzato per l'associazione locale dei risultati monadici.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

dove

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

La funzione di guardia è definita

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

dove il tipo di unità o "tupla vuota"

data () = ()

Le monadi additive che supportano la scelta e il fallimento possono essere astratte usando una classe di tipo

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 = []
   (<|>) = (++)

dove faile <|>formare un monoideforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

ed failè l'elemento zero assorbente / annichilente delle monadi additive

_ =<< fail  =  fail

Se dentro

guard (even p) >> return p

even pè vero, quindi la guardia produce [()]e, per definizione >>, la funzione costante locale

\ _ -> return p

viene applicato al risultato (). Se falso, la guardia produce la lista monad's fail( []), che non produce alcun risultato per l'applicazione di una freccia Kleisli >>, quindi questa pviene saltata.

Stato

Stranamente, le monadi sono usate per codificare il calcolo con stato.

Un processore di stato è una funzione

forall st t. st -> (t, st)

che transita uno stato ste produce un risultato t. Lo stato st può essere qualsiasi cosa. Niente, bandiera, conteggio, matrice, maniglia, macchina, mondo.

Il tipo di processori di stato viene solitamente chiamato

type State st t = st -> (t, st)

La monade processore stato è la kinded * -> *functor State st. Le frecce Kleisli della monade del processore di stato sono funzioni

forall st a b. a -> (State st) b

In canonico Haskell viene definita la versione pigra della monade del processore di stato

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

Un processore di stato viene eseguito fornendo uno stato iniziale:

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

L'accesso allo stato è fornito da primitivi gete put, metodi di astrazione su monadi statali :

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdichiara una dipendenza funzionale del tipo di stato stsulla monade m; che a State t, ad esempio, determinerà che il tipo di stato sia tunivoco.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

con il tipo di unità usato in modo analogo a voidin C.

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 viene spesso utilizzato con gli accessi ai campi record.

L'equivalente monade statale del threading variabile

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

dove s0 :: Int, è ugualmente referenzialmente trasparente, ma infinitamente più elegante e pratico

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)è un calcolo di tipo State Int (), ad eccezione del suo effetto equivalente a return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

La legge monade dell'associatività può essere scritta in termini di >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

o

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                 }
}                    }

Come nella programmazione orientata all'espressione (ad esempio Rust), l'ultima istruzione di un blocco rappresenta il suo rendimento. L'operatore di bind viene talvolta chiamato "punto e virgola programmabile".

Le primitive della struttura di controllo dell'iterazione dalla programmazione imperativa strutturata sono emulate monadicamente

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

Input Output

data World

La monade del processore di stato del mondo I / O è una riconciliazione di Haskell puro e del mondo reale, della semantica operativa denotativa e imperativa funzionale. Un analogo stretto dell'attuazione rigorosa effettiva:

type IO t = World -> (t, World)

L'interazione è facilitata da primitivi impuri

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

L'impurità del codice che utilizza le IOprimitive è stabilita in modo permanente dal sistema dei tipi. Perché la purezza è fantastica, ciò che accade dentro IOrimane dentro IO.

unsafePerformIO :: IO t -> t

O, almeno, dovrebbe.

La firma del tipo di un programma Haskell

main :: IO ()
main = putStrLn "Hello, World!"

si espande a

World -> ((), World)

Una funzione che trasforma un mondo.

Epilogo

La categoria quali oggetti sono tipi di Haskell e quali morfismi sono funzioni tra i tipi di Haskell è, "veloce e sciolto", la categoria Hask .

Un functor Tè una mappatura da una categoria Ca una categoria D; per ogni oggetto in Cun oggetto inD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

e per ogni morfismo in Cun morfismo inD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

dove X, Ysono oggetti C. HomC(X, Y)è la classe di omomorfismo di tutti i morfismi X -> Yin C. Il funzionario deve preservare l'identità e la composizione del morfismo, la "struttura" di C, in D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

La categoria Kleisli di una categoria Cè data da una tripla Kleisli

<T, eta, _*>

di un endofunctor

T : C -> C

( f), un morfismo di identità eta( return) e un operatore di estensione *(=<< ).

Ogni morfismo Kleisli in Hask

      f :  X -> T(Y)
      f :: a -> m b

dall'operatore di estensione

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

viene dato un morfismo nella Haskcategoria Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

La composizione nella categoria Kleisli .Tè data in termini di estensione

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

e soddisfa gli assiomi di categoria

       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

quale, applicando le trasformazioni di equivalenza

     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

in termini di estensione sono dati canonicamente

               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

Le monadi possono anche essere definite in termini non di estensione kleisliana, ma di una naturale trasformazione mu, in programmazione chiamata join. Una monade è definita in termini di mutripla su una categoria C, di un endofunctor

     T :  C -> C
     f :: * -> *

e due trasformazioni naturali

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

soddisfacendo le equivalenze

       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

Viene quindi definita la classe del tipo di monade

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

L' muimplementazione canonica dell'opzione monade:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

La concatfunzione

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

è la joinmonade della lista.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Le implementazioni di joinpossono essere tradotte dal modulo di estensione usando l'equivalenza

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

La traduzione inversa da mual modulo di estensione è data da

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Ma perché una teoria così astratta dovrebbe essere di qualche utilità per la programmazione?

La risposta è semplice: come scienziati informatici, apprezziamo l'astrazione ! Quando progettiamo l'interfaccia per un componente software, vogliamo che riveli il meno possibile sull'implementazione. Vogliamo essere in grado di sostituire l'implementazione con molte alternative, molte altre "istanze" dello stesso "concetto". Quando progettiamo un'interfaccia generica per molte librerie di programmi, è ancora più importante che l'interfaccia che scegliamo abbia una varietà di implementazioni. È la generalità del concetto di monade che apprezziamo così tanto, è perché teoria delle categorie è così astratta che i suoi concetti sono così utili per la programmazione.

Non sorprende, quindi, che la generalizzazione delle monadi che presentiamo di seguito abbia anche una stretta connessione con la teoria delle categorie. Ma sottolineiamo che il nostro scopo è molto pratico: non è "implementare la teoria delle categorie", è trovare un modo più generale di strutturare le librerie combinatrici. È semplicemente una nostra fortuna che i matematici abbiano già svolto gran parte del lavoro per noi!

da Generalising Monads a Arrows di John Hughes


4

Ciò di cui il mondo ha bisogno è un altro post sul blog di una monade, ma penso che ciò sia utile per identificare le monadi esistenti in natura.

Triangolo di Sierpinski

Quanto sopra è un frattale chiamato triangolo Sierpinski, l'unico frattale che ricordo di aver disegnato. I frattali sono strutture auto-simili come il triangolo sopra, in cui le parti sono simili al tutto (in questo caso esattamente la metà della scala come triangolo genitore).

Le monadi sono frattali. Data una struttura di dati monadica, i suoi valori possono essere composti per formare un altro valore della struttura di dati. Questo è il motivo per cui è utile per la programmazione, ed è per questo che si verifica in molte situazioni.


3
Intendi "ciò di cui il mondo non ha bisogno ..."? Bella analogia però!
Groverboy,

@ icc97 hai ragione: il significato è abbastanza chiaro. Sarcasmo involontario, scuse all'autore.
Groverboy,

Ciò di cui il mondo ha bisogno è un altro thread di commenti che confermi un sarcasmo, ma se letto attentamente ho scritto ma quindi dovrebbe essere chiaro.
Eugene Yokota,


4

Lascia che il " {| a |m}" sottostante rappresenti alcuni dati monadici. Un tipo di dati che pubblicizza un a:

        (I got an a!)
          /        
    {| a |m}

Funzione, fsa come creare una monade, se solo avesse un a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Qui vediamo funzione, fcerca di valutare una monade ma viene rimproverato.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion,, ftrova il modo di estrarre autilizzando >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Poco lo fsa, la monade e >>=sono in collusione.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Ma di cosa parlano effettivamente? Bene, dipende dalla monade. Parlare esclusivamente in astratto ha un uso limitato; devi avere una certa esperienza con monadi particolari per arricchire la comprensione.

Ad esempio, il tipo di dati Forse

 data Maybe a = Nothing | Just a

ha un'istanza monade che agirà come il seguente ...

In questo caso, se è il caso 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

Ma per il caso di 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}

Quindi la monade Maybe consente a un calcolo di continuare se contiene effettivamente aciò che pubblicizza, ma in caso contrario interrompe il calcolo. Il risultato, tuttavia, è ancora un pezzo di dati monadici, sebbene non l'output dif . Per questo motivo, la monade Maybe viene utilizzata per rappresentare il contesto del fallimento.

Monadi diverse si comportano diversamente. Gli elenchi sono altri tipi di dati con istanze monadiche. Si comportano come segue:

(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  

In questo caso, la funzione sapeva come creare un elenco dal suo input, ma non sapeva cosa fare con input e liste extra. Il legame >>=, aiutato fcombinando le uscite multiple. Includo questo esempio per dimostrare che mentre >>=è responsabile dell'estrazione a, ha anche accesso all'eventuale output associato di f. In effetti, non ne estrarrà mai nessuno a ameno che non sappia che l'eventuale output ha lo stesso tipo di contesto.

Ci sono altre monadi che sono usate per rappresentare contesti diversi. Ecco alcune caratterizzazioni di alcuni altri. La IOmonade in realtà non ne ha una a, ma conosce un ragazzo e lo otterrà aper te. La State stmonade ha una scorta segreta di stciò che passerà fsotto il tavolo, anche se è fappena venuto a chiedere un a. La Reader rmonade è simile a State st, anche se lascia solo fguardare r.

Il punto in tutto ciò è che qualsiasi tipo di dati che si dichiara essere una Monade sta dichiarando una sorta di contesto attorno all'estrazione di un valore dalla Monade. Il grande guadagno da tutto questo? Bene, è abbastanza facile elaborare un calcolo con una sorta di contesto. Può diventare disordinato, tuttavia, quando si mettono insieme più calcoli carichi di contesto. Le operazioni della monade si occupano di risolvere le interazioni del contesto in modo che il programmatore non debba farlo.

Si noti che l'uso di questo >>=facilita il disordine togliendo parte dell'autonomia da f. Cioè, nel caso di cui sopra Nothingper esempio, fnon è più possibile decidere cosa fare nel caso di Nothing; è codificato >>=. Questo è il compromesso. Se fosse necessario per fdecidere cosa fare in caso Nothing, allora favrebbe dovuto essere una funzione da Maybe aa Maybe b. In questo caso, Maybeessere una monade è irrilevante.

Nota, tuttavia, che a volte un tipo di dati non esporta i suoi costruttori (ti guarda IO) e se vogliamo lavorare con il valore pubblicizzato non abbiamo altra scelta che lavorare con la sua interfaccia monadica.


3

Una monade è una cosa usata per incapsulare oggetti che cambiano stato. Si riscontra più spesso in lingue che altrimenti non consentono di avere uno stato modificabile (ad esempio, Haskell).

Un esempio potrebbe essere per l'I / O dei file.

Saresti in grado di utilizzare una monade per l'I / O dei file per isolare la natura mutevole dello stato solo al codice che utilizzava la Monade. Il codice all'interno della Monade può effettivamente ignorare il mutevole stato del mondo al di fuori della Monade - questo rende molto più facile ragionare sull'effetto complessivo del tuo programma.


3
A quanto ho capito, le monadi sono più di questo. Incapsulare lo stato mutevole in linguaggi funzionali "puri" è solo un'applicazione delle monadi.
thoft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.