[haskell] Cos'è una monade?


14 Answers

Spiegare "cos'è una monade" è un po 'come dire "che cos'è un numero?" Usiamo i numeri tutto il tempo. Ma immagina di aver incontrato qualcuno che non sapeva nulla dei numeri. Come diamine vuoi spiegare quali sono i numeri? E come inizieresti a descrivere il motivo per cui potrebbe essere utile?

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

In sostanza, stai scrivendo passi di esecuzione e li colleghi insieme alla "funzione di bind". (In Haskell, si chiama >>= .) Puoi scrivere le chiamate all'operatore di binding da solo, oppure puoi utilizzare lo zucchero di sintassi che fa sì che il compilatore inserisca quelle chiamate di funzione per te. Ma in entrambi i casi, ogni passo è separato da una chiamata a questa funzione di bind.

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

Non sembra troppo difficile, giusto? Ma c'è più di un tipo di monade. Perché? Come?

Bene, la funzione bind può solo prendere il risultato da un passo e passarlo al passo successivo. Ma se questo è "tutto", la monade lo fa ... che in realtà non è molto utile. E questo è importante per capire: ogni monade utile fa qualcos'altro oltre a essere solo una monade. Ogni monade utile ha un "potere speciale", che lo rende unico.

(Una monade che non fa nulla di speciale è chiamata "identità monad": piuttosto come la funzione di identità, sembra una cosa assolutamente inutile, ma risulta non essere ... Ma questa è un'altra storia ™).

Fondamentalmente, ogni monade ha la sua implementazione della funzione bind. Ed è possibile scrivere una funzione di bind in modo tale da eseguire operazioni di hoopy tra le fasi di esecuzione. Per esempio:

  • Se ogni passaggio restituisce un indicatore di successo / fallimento, è possibile eseguire il bind del passaggio successivo solo se il precedente è riuscito. In questo modo, un passo fallito interrompe "automaticamente" l'intera sequenza, senza alcun test condizionale da parte tua. (The Failure Monad .)

  • Estendendo questa idea, puoi implementare "eccezioni". (The Error Monad o Exception Monad .) Dato che li stai definendo da soli piuttosto che essere una funzione linguistica, puoi definire come funzionano. Ad esempio, potresti voler ignorare le prime due eccezioni e interrompere solo quando viene lanciata una terza eccezione.

  • Puoi fare in modo che ogni passo restituisca più risultati e fare in modo che la funzione di bind esegua il loop su di essi, alimentando ciascuno il passaggio successivo per te. In questo modo, non devi continuare a scrivere loop dappertutto quando si hanno a che fare con più risultati. La funzione di bind "automaticamente" fa tutto questo per te. (The List Monad .)

  • Oltre a passare un "risultato" da un passaggio a un altro, è possibile fare in modo che la funzione bind trasmetta anche altri dati . Questi dati ora non vengono visualizzati nel codice sorgente, ma puoi comunque accedervi da qualsiasi luogo, senza doverli passare manualmente a tutte le funzioni. (The Reader Monad .)

  • Puoi fare in modo che i "dati extra" possano essere sostituiti. Ciò consente di simulare aggiornamenti distruttivi , senza effettuare effettivamente aggiornamenti distruttivi. (La Monade di Stato e suo cugino, lo scrittore Monad .)

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

  • È possibile creare una monade in cui i calcoli possono essere sospesi , in modo da poter mettere in pausa il programma, entrare e armeggiare con i dati di stato interni e quindi riprenderlo.

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

Tutto questo e molto altro è possibile con le monadi. Naturalmente, tutto questo è perfettamente possibile anche senza monadi. È semplicemente più facile usare le monadi.

Question

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

Ho trovato molte delle spiegazioni che ho incontrato per essere abbastanza inaccessibili e prive di dettagli pratici.




(Vedi anche le risposte a Che cos'è una monade? )

Una buona motivazione per Monads è " Potresti aver inventato le Monadi " di Dan Piponi ! (E forse hai già) . Ci sono molti altri tutorial di monad , molti dei quali cercano erroneamente di spiegare le monadi in "termini semplici" usando varie analogie: questa è la fallacia del tutorial monad ; evitali.

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

Quindi, le cose che odio di Haskell:

Iniziamo con l'ovvio. Esercitazioni su Monad. No, non monadi. In particolare le esercitazioni. Sono infiniti, esagerati e caro dio sono noiosi. Inoltre, non ho mai visto alcuna prova convincente che loro effettivamente aiutino. Leggi la definizione della classe, scrivi del codice, supera il nome spaventoso.

Dici di capire la Forse monade? Bene, sei sulla buona strada. Inizia a usare altre monadi e prima o poi capirai in generale le monadi.

[Se sei orientato matematicamente, potresti voler ignorare le dozzine di tutorial e imparare la definizione, o seguire lezioni in teoria delle categorie :) La parte principale della definizione è che un Monad M implica un "tipo costruttore" 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 articoli accademici che introducono le monadi, le Monadi di Philip Wadler per la programmazione funzionale . In realtà ha esempi pratici, non banali , a differenza di molti tutorial artificiali là fuori.







Ho pensato di Monadi in un modo diverso, ultimamente. Ho pensato a loro come astrazione fuori ordine di esecuzione in modo matematico, il che rende nuovi tipi di polimorfismo possibile.

Se stai usando un linguaggio imperativo, e si scrive alcune espressioni in ordine, il codice viene eseguito sempre esattamente in questo ordine.

E nel caso semplice, quando si utilizza una monade, ci si sente lo stesso - si definisce un elenco di espressioni che accadono in ordine. Solo che, a seconda di quale monade si utilizza, il codice potrebbe funzionare in modo (come in IO Monade), in parallelo su più elementi contemporaneamente (come nella lista Monade), potrebbe bloccare parzialmente attraverso (come nel Forse Monade) , potrebbe mettere in pausa partway attraverso essere ripresa in seguito (come in una monade Ripresa), potrebbe riavvolgere e ricominciare dall'inizio (come in una monade transazione), oppure potrebbe riavvolgere partway per cercare altre opzioni (come in una monade Logic) .

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

Inoltre, in alcuni casi, è possibile combinare monadi insieme (con trasformatori monade) per ottenere molteplici funzioni allo stesso tempo.




Una monade è un modo di combinare insieme i calcoli che condividono un contesto comune. E 'come la costruzione di una rete di tubazioni. Quando si costruisce la rete, non ci sono dati che lo attraversano. Ma quando ho finito mettendo tutti i pezzi insieme a 'bind' e 'ritorno' quindi invoco qualcosa di simile runMyMonad monad dataed i flussi di dati attraverso i tubi.




Principessa spiegazione s' di F # Computation Espressioni mi ha aiutato, anche se non riesco ancora a dire che ho davvero capito.

EDIT : questa serie - spiega monadi con javascript - è quello che 'pendere la bilancia' per me.

Penso che la comprensione monadi è qualcosa che si insinua su di voi. In questo senso, la lettura come molti 'tutorial' possibile è una buona idea, ma roba spesso strano (lingua sconosciuta o sintassi) impedisce il cervello di concentrarsi sull'essenziale.

Alcune cose che ho avuto difficoltà a capire:

  • Spiegazioni basato su regole mai lavorato per me, perché esempi più pratici in realtà richiedono più di un semplice ritorno / bind.
  • Inoltre, li chiama regole non ha aiutato. E 'più di un caso di "ci sono queste cose che hanno qualcosa in comune, chiamiamoli le cose 'monadi', e i bit di 'regole' comuni".
  • Return ( a -> M<a>) e Bind ( M<a> -> (a -> M<b>) -> M<b>) sono grandi, ma quello che non ho mai potuto capire è come Bind potrebbe estrarre il ada M<a>per passare in a -> M<b>. Io non credo di aver mai letto da nessuna parte (forse è ovvio per tutti gli altri), che l'inverso di ritorno ( M<a> -> a) deve esistere all'interno della monade, semplicemente non ha bisogno di essere esposti.



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

Considerate queste tre funzioni in pseudocodice:

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

fprende una coppia ordinata della forma <x, messages>e restituisce una coppia ordinata. Lascia il primo elemento intatta e aggiunge "called f. "al secondo punto. Stessa cosa con g.

È possibile comporre queste funzioni e ottenere il vostro valore originale, insieme a una stringa che indica che ordine le funzioni sono stati chiamati in:

  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. (Provate a immaginare per amor di discussione che invece di stringhe allegate, fe gdeve eseguire la logica complessa sul secondo punto della coppia Sarebbe un dolore per ripetere che la logica complessa in due -. O più -. Diverse funzioni)

Si preferisce scrivere funzioni più semplici:

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

Ma guardate cosa succede quando li si compone:

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

Il problema è che il passaggio di una coppia in una funzione non ti dà quello che vuoi. Ma cosa succede se si potrebbe nutrire 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 "nutrire 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>

Si noti che cosa succede quando si fanno tre cose con le funzioni:

Primo: se si avvolge un valore e quindi nutrire 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)

Che è lo stesso come passa il valore nella funzione.

Secondo: se si alimenta una coppia in 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>

Questo non cambia la coppia.

Terzo: se si definisce una funzione che prende xe alimenta g(x)in f:

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

e alimentare un paio 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>))

Che è lo stesso come alimentando la coppia in ge alimentando la coppia risultante in f.

Hai più di una monade. Ora non vi resta che conoscere i tipi di dati nel programma.

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

Mè un costruttore di tipo: Mda solo non fa riferimento a un tipo, ma M _si riferisce a un tipo una volta che riempire il vuoto con un tipo. Una M intè una coppia di un int e una stringa. Una M stringè una coppia di una stringa e una stringa. Eccetera.

Congratulazioni, avete creato una monade!

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

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

  • M è un costruttore di tipo.
  • feedprende un (funzione che prende una te restituisce un M u) e un M te restituisce un M u.
  • wrapprende un ve restituisce un M v.

t, uE vsono eventuali tre tipi che possono o non possono essere la stessa cosa. Una monade soddisfa le tre proprietà che ha dimostrato per la monade specifica:

  • Alimentando una avvolto tin una funzione è la stessa che passa l'scartare tnella funzione.

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

  • Alimentare un M tin wrapnon fa nulla per il M t.

    formalmente: feed(wrap, m) = m

  • Alimentare un M t(chiamare m) in una funzione che

    • passa il ting
    • Ottiene un M u(chiamare n) dag
    • alimenta ninf

    equivale a

    • alimentando ming
    • ricevendo 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 wrapviene chiamato return.




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

Considerando divisione accetta due fractionals, e restituisce una frazionata, che definisce la divisione per zero come infinito in haskell somewhy (che risulta essere un somewhy frazionaria) ...

In ogni caso, appare Monadi sono solo un modo per garantire che la catena di operazioni si comporta in modo prevedibile, e una funzione che sostiene di essere Num -> Num, composta con un'altra funzione di NUM-> Num chiamato con x no per esempio, sparare i missili.

D'altra parte, se abbiamo una funzione che fa sparare i missili, siamo in grado di comporre con altre funzioni che il fuoco anche i missili, perché il nostro intento è chiaro - vogliamo lanciare i missili - ma non cercheremo stampa "Ciao mondo" per qualche strana ragione.

In Haskell, principale è di tipo IO (), o IO [()], il distiction è strano e non voglio discuterne ma ecco quello che penso succede:

Se ho principale, voglio che faccia una catena di azioni, la ragione per cui eseguire il programma è quello di produrre un effetto - di solito però IO. Così posso operazioni catena IO insieme in principale al fine di fare - IO, nient'altro.

Se provo a fare qualcosa che non "tornare IO", il programma si lamenterà che la catena non scorre, o fondamentalmente "Come questo si riferisce a ciò che stiamo cercando di fare - un'azione IO", appare per forza il programmatore per mantenere il filo del discorso, senza allontanarsi fuori e pensare a sparare i missili, mentre la creazione di algoritmi per l'ordinamento - che non scorre.

In sostanza, Monadi sembrano essere un suggerimento per il compilatore che "ehi, lo sai questa funzione che restituisce un numero qui, in realtà non sempre funziona, a volte può produrre un numero, e talvolta Niente di niente, basta tenere a mente". Sapendo questo, se si tenta di affermare un'azione monadica, l'azione della monade può agire come un'eccezione momento della compilazione dicendo "hey, questo non è in realtà un numero, questo può essere un numero, ma non si può assumere questo, fare qualcosa per garantire che il flusso è accettabile ". che impedisce il comportamento del programma imprevedibile - in misura equa.

Sembra monadi non riguardano la purezza, né il controllo, ma di mantenere un'identità di una categoria sulla quale tutto il comportamento è prevedibile e definita, o non viene compilato. Non si può fare nulla quando si sono tenuti a fare qualcosa, e non si può fare qualcosa, se ci si aspetta di fare nulla (visibile).

Il più grande motivo mi veniva in mente per Monadi è - andare a vedere il codice / OOP procedurale, e si noterà che non si sa dove si avvia il programma, né finisce, tutto quello che vedete è un sacco di salto e un sacco di matematica , magia, e missili. Non sarà in grado di mantenere, e se è possibile, vi permetterà di trascorrere un bel po 'di tempo avvolgere la tua mente intorno l'intero programma prima di poter comprendere una parte di esso, in quanto la modularità in questo contesto si basa su "sezioni" interdipendenti di codice, in cui il codice è ottimizzato per essere il relativo possibile per promessa di efficienza / interrelazione. Monadi sono molto concreti e ben definiti per definizione, e garantire che il flusso del programma è possibile analizzare e isolare le parti che sono difficili da analizzare - come essi stessi sono monadi. Una monade sembra essere un "Unità comprensibile che è prevedibile al momento della sua piena comprensione "- Se si capisce 'Forse' monade, non c'è modo possibile, farà di tutto tranne che essere 'Forse', che appare banale, ma nella maggior parte del codice non monade, una semplice funzione" HelloWorld "può sparare i missili, non fare nulla, o distruggere l'universo o addirittura alterare il tempo -.. non abbiamo idea né hanno alcuna garanzia che l'IT è quello che è A gARANZIE monade che l'IT è quello che è, che è molto potente.o distruggere l'universo o addirittura alterare il tempo - non abbiamo idea né hanno alcuna garanzia che l'IT è quello che è. Una monade garantisce di è quello che è. che è molto potente.o distruggere l'universo o addirittura alterare il tempo - non abbiamo idea né hanno alcuna garanzia che l'IT è quello che è. Una monade garantisce di è quello che è. che è molto potente.

Tutte le cose nel "mondo reale" sembrano essere monadi, nel senso che esso è vincolato da leggi precise osservabili prevenire confusione. Questo non significa che dobbiamo imitare tutte le operazioni di questo oggetto per creare classi, invece possiamo semplicemente dire "una piazza è una piazza", nient'altro che una piazza, nemmeno un rettangolo, né un cerchio, e "un quadrato ha un'area della lunghezza di uno dei suoi esistenti dimensioni moltiplicato per se stesso. non importa quale piazza che hai, se si tratta di una piazza nello spazio 2D, è un'area assolutamente non può essere altro che la sua lunghezza al quadrato, è quasi banale da dimostrare. Questo è molto potente perché non abbiamo bisogno di fare affermazioni per assicurarsi che il nostro mondo è il modo in cui è, usiamo appena implicazioni della realtà per evitare che i nostri programmi di cadere fuori pista.

Im praticamente garantito per essere sbagliato, ma credo che questo potrebbe aiutare qualcuno là fuori, quindi speriamo che aiuta qualcuno.




In realtà, contrariamente alla comune comprensione della Monade, non hanno nulla a che fare con lo stato. Le Monade sono semplicemente un modo per avvolgere le cose e fornire metodi per fare operazioni sulle cose incartate senza scartarle.

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

data Wrapped a = Wrap a

Per avvolgere cose che definiamo

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

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

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

Questo è tutto ciò che c'è da capire. Tuttavia, risulta 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

bind può fare un po 'più di fmap , ma non viceversa. In realtà, fmap può essere definita solo in termini di bind e return . Quindi, quando si definisce una monade, si dà il suo tipo (qui era Wrapped a ) e poi si dice come funzionano le operazioni di return e di bind .

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

Per un buon articolo su come le monadi possono essere utilizzate per introdurre dipendenze funzionali e quindi controllare l'ordine di valutazione, come se fosse usato nella monade I / O di Haskell, date un'occhiata a IO Inside .

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




Monadi sono per controllare il flusso cosa tipi di dati astratti sono i dati.

In altre parole, molti sviluppatori sono confortevoli, con l'idea di insiemi, liste, dizionari (o hash, o mappe), ed alberi. All'interno di questi tipi di dati ci sono molti casi particolari (per esempio InsertionOrderPreservingIdentityHashMap).

Tuttavia, di fronte a "flusso" programma di molti sviluppatori non sono stati esposti a molti altri costrutti che se, switch / case, fare, mentre, goto (GRR), e (forse) chiusure.

Quindi, una monade è semplicemente un costrutto flusso di controllo. Una frase meglio sostituire monade sarebbe 'tipo di controllo'.

Come tale, una monade è dotato di slot per la logica di controllo, o di dichiarazioni o le funzioni - l'equivalente in strutture di dati potrebbe essere quella di dire che alcune strutture di dati consentono di aggiungere i dati, e rimuoverlo.

Ad esempio, il "caso" Monade:

if( clause ) then block

Nel caso più semplice dispone di due alloggiamenti - una clausola, e un blocco. La ifMonade è di solito costruito per valutare il risultato della clausola, e se non falsa, valutare il blocco. Molti sviluppatori non vengono introdotti a monadi quando vengono a sapere 'se', e semplicemente non è necessario capire monadi di scrivere la logica efficace.

Monadi può diventare più complicato, nello stesso modo in cui le strutture di dati può diventare più complicato, ma ci sono molti grandi categorie di monade che possono avere una semantica simili, ma differenti implementazioni e sintassi.

Naturalmente, nello stesso modo in cui le strutture di dati possono essere iterate su, o attraversati, possono essere valutati monadi.

I compilatori possono o non possono avere il supporto per monadi definiti dall'utente. Haskell fa certamente. Ioke ha alcune funzionalità simili, anche se il termine Monade non è utilizzato nel linguaggio.




Monadi Non sono metafore , ma praticamente utile un'astrazione che emerge da un modello comune, come spiega Daniel Spiewak.




Nel contesto della Scala si trova il seguente per essere la definizione più semplice. Fondamentalmente flatMap (o bind) è 'associativa' ed 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 una Monade nella programmazione funzionale non è la stessa come la definizione di una Monade in Teoria Categoria , che è definito a turno di mape flatten. Anche se sono una specie di equivalente in determinate mappature. Questa presentazione è molto buona: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category




Una monade è una cosa utilizzato per incapsulare gli oggetti che sono mutevoli Stato. E 'più spesso incontrato in lingue che altrimenti non ti permettono di avere stato modificabili (per esempio, Haskell).

Un esempio potrebbe essere per il file di I / O.

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




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

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

Un functor è qualsiasi tipo di costruzione T per cui esiste una funzione di ordine superiore, chiamala map , che trasforma una funzione di tipo a -> b (dato qualsiasi due tipi a e b ) in una funzione T a -> T b . Questa funzione map deve anche obbedire alle leggi dell'identità e della composizione in modo tale che le seguenti espressioni restituiscano true per tutte le x , p q (notazione Haskell):

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

Ad esempio, un costruttore di tipi chiamato List è un functor se è dotato di una funzione di tipo (a -> b) -> List a -> List b che obbedisce alle leggi precedenti. L'unica implementazione pratica è ovvia. L' List a -> List b risultante List a -> List b funziona itera sull'elenco dato, chiamando la funzione (a -> b) per ciascun elemento e restituisce l'elenco dei risultati.

Una monade è essenzialmente solo un functor T con due metodi extra, join , di tipo T (T a) -> T a , e unit (a volte chiamato return , fork o pure ) di tipo a -> T a . Per gli elenchi in Haskell:

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

Perché è utile? Perché potresti, per esempio, map su un elenco con una funzione che restituisce una lista. Join prende l'elenco risultante di elenchi e li concatena. List è una monade perché è possibile.

Puoi scrivere una funzione che map , quindi join . Questa funzione è chiamata bind , o flatMap , o (>>=) o (=<<) . Questo è normalmente il modo in cui viene fornita un'istanza monad in Haskell.

Una monade deve soddisfare certe leggi, e cioè che l' join deve essere associativa. Ciò significa che se si ha un valore x di tipo [[[a]]] quindi join (join x) deve essere uguale a join (map join x) . E pure deve essere un'identità per join tale che si join (pure x) == x .




Dopo tanto sforzo, credo di aver finalmente capito la monade. Dopo aver riletto la mia lunga critica della schiacciante superiore votato risposta, mi offrirà questa spiegazione.

Ci sono tre domande che devono essere risolte per capire monadi:

Perché avete bisogno di una monade? Che cosa è una monade? Come viene implementata una monade?

Come ho notato nei miei commenti originali, troppe spiegazioni monade farsi prendere in esame il numero 3, senza, e prima che in realtà adeguatamente coprire la domanda 2, o domanda 1.

Perché avete bisogno di una monade?

Pure linguaggi funzionali come Haskell sono diversi dai linguaggi imperativi come C o Java in quanto, un programma funzionale puro non è necessariamente eseguita in un ordine specifico, un passo alla volta. Un programma Haskell è più simile a una funzione matematica, in cui è possibile risolvere il "equazione" in qualsiasi numero di potenziali ordini. Questo conferisce una serie di vantaggi, tra i quali è che elimina la possibilità di certi tipi di insetti, 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 file di I / O, hanno bisogno di cose accadano in un ordine particolare, o hanno bisogno di mantenere lo stato. Un modo per affrontare questo problema è quello di creare una sorta di oggetto che rappresenta lo stato di un calcolo, e una serie di funzioni che richiedono un oggetto di stato come input, e restituire un nuovo oggetto stato modificato.

quindi cerchiamo di creare un ipotetico "stato" del valore, che rappresenta lo stato di uno schermo della console. esattamente come questo valore è costruito non è importante, ma diciamo che è un array di caratteri ASCII di lunghezza di 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, modificarlo, e restituiscono un nuovo stato della console.

consolestate MyConsole = new consolestate;

in modo da fare la programmazione della console, ma in maniera funzionale puro, si avrebbe bisogno di nido un sacco di chiamate di funzione all'interno vicenda.

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

Programmazione in questo modo mantiene lo stile "puro" funzionale, mentre costringendo modifiche alla console per accadere in un ordine particolare. Ma, noi probabilmente vogliamo fare più di un semplice paio di operazioni in un momento come nell'esempio di cui sopra. funzioni di nidificazione in questo modo inizierà a diventare sgraziato. Quello che vogliamo, è il codice che fa essenzialmente la stessa cosa come sopra, ma è scritto un po 'più simile a questo:

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

questo sarebbe davvero un modo più conveniente per scriverlo. Come lo facciamo però?

Che cosa è una monade?

una volta che si dispone di un tipo (ad esempio consolestate) che si definisce insieme ad un gruppo di funzioni progettate specificamente per operare su quel tipo, è possibile ruotare l'intero pacchetto di queste cose in un "monade" definendo un operatore come :(bind) che automaticamente alimenta valori restituiti alla sua sinistra, in parametri funzionali alla sua destra, e un liftoperatore che trasforma normali funzioni, in funzioni che funzionano con quel particolare tipo di operatore bind.

Come viene implementata una monade?

Vedi altre risposte, che sembrano abbastanza liberi di saltare nei dettagli di quello.






Related