Cosa sono le monadi libere?


368

Ho visto il termine libero Monade pop-up ogni ora e poi per qualche tempo, ma tutti sembrano solo per usare / discuterne senza dare una spiegazione di ciò che sono. Quindi: cosa sono le monadi libere? (Direi che ho familiarità con le monadi e le basi di Haskell, ma ho solo una conoscenza molto approssimativa della teoria delle categorie.)


12
Una spiegazione abbastanza buona è qui haskellforall.com/2012/06/…
Roger Lindsjö

19
@Roger è una specie di pagina che mi ha portato qui. Per me, quell'esempio definisce un'istanza di monade per un tipo chiamato "Free" e basta.
David,

Risposte:


295

La risposta di Edward Kmett è ovviamente eccezionale. Ma è un po 'tecnico. Ecco una spiegazione forse più accessibile.

Le monadi libere sono solo un modo generale di trasformare i funzioni in monadi. Cioè, dato che qualsiasi funzione f Free fè una monade. Questo non sarebbe molto utile, a meno che tu non abbia un paio di funzioni

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

il primo di questi ti permette di "entrare" nella tua monade, e il secondo ti dà un modo per "uscirne".

Più in generale, se X è una Y con alcune cose extra P, allora una "X libera" è un modo per passare da una Y a una X senza guadagnare nulla in più.

Esempi: un monoide (X) è un insieme (Y) con struttura extra (P) che sostanzialmente dice che ha un'operazione (puoi pensare all'addizione) e una certa identità (come zero).

Così

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

Ora conosciamo tutti gli elenchi

data [a] = [] | a : [a]

Bene, dato qualsiasi tipo tsappiamo che [t]è un monoide

instance Monoid [t] where
  mempty   = []
  mappend = (++)

e quindi le liste sono il "monoide libero" sui set (o nei tipi di Haskell).

Ok, quindi le monadi libere sono la stessa idea. Prendiamo un funzione e restituiamo una monade. Infatti, poiché le monadi possono essere viste come monoidi nella categoria degli endofunctor, la definizione di un elenco

data [a] = [] | a : [a]

assomiglia molto alla definizione di monadi libere

data Free f a = Pure a | Roll (f (Free f a))

e l' Monadistanza ha una somiglianza con l' Monoidistanza per gli elenchi

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

ora otteniamo le nostre due operazioni

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)

12
Questa potrebbe essere la migliore spiegazione accessibile di "libero" che abbia mai visto. Soprattutto il paragrafo che inizia con "Più in generale".
John L

16
Penso che sia interessante guardare Free f a = Pure a | Roll (f (Free f a))come Free f a = a + fa + ffa + ..., cioè "f applicato a un numero qualsiasi di volte". Quindi concatFree(ie join) accetta una "f applicata un numero qualsiasi di volte a (f applicata un numero qualsiasi di volte a a)" e comprime le due applicazioni nidificate in una. E >>=prende "f applicato un numero qualsiasi di volte a a" e "come ottenere da a a (b con f applicato un numero qualsiasi di volte)", e fondamentalmente applica quest'ultimo a uno all'interno del primo e fa crollare l'annidamento. Ora anch'io lo capisco!
jkff,

1
è concatFreefondamentalmente join?
Rgrinberg,

11
“Ecco una spiegazione forse più accessibile. [...] In effetti, dal momento che le monadi possono essere viste come monoidi nella categoria dei funzioni endo, ... "Tuttavia, penso che questa sia un'ottima risposta.
Ruud,

2
"monadi possono essere visti come monoidi nella categoria di endo funtori" <3 (si dovrebbe collegare a stackoverflow.com/a/3870310/1306877 perché ogni haskeller dovrebbe conoscere che il riferimento!)
tlo

418

Ecco una risposta ancora più semplice: una Monade è qualcosa che "calcola" quando il contesto monadico è crollato join :: m (m a) -> m a(ricordando che >>=può essere definito come x >>= y = join (fmap y x)). Ecco come le Monadi trasportano il contesto attraverso una catena sequenziale di calcoli: perché in ogni punto della serie, il contesto della chiamata precedente viene compresso con il successivo.

Una monade libera soddisfa tutte le leggi della Monade, ma non fa alcun crollo (cioè calcolo). Crea semplicemente una serie nidificata di contesti. L'utente che crea un valore monadico così libero è responsabile di fare qualcosa con quei contesti nidificati, in modo che il significato di tale composizione possa essere rinviato fino a dopo che il valore monadico è stato creato.


8
I tuoi paragrafi sono davvero una grande aggiunta al post di Philip.
David,

20
Mi piace molto questa risposta.
danidiaz,

5
La monade libera può sostituire la classe di tipo Monade? Cioè, potrei scrivere un programma usando solo il ritorno e il bind della monade libera, e quindi unire i risultati usando quello che preferisco di Mwybe o List o qualsiasi altra cosa, o persino generare più viste monadiche di una sequenza di chiamate di funzione associate / concattate. Ignorando bottom e nontermination, cioè.
misterbee,

2
Questa risposta mi ha aiutato, ma penso che mi avrebbe confuso se non avessi incontrato 'iscriviti' al corso NICTA e letto haskellforall.com/2012/06/… . Quindi per me, il trucco per capire è leggere molte risposte fino a quando non affonda. (Riferimento NICTA: github.com/NICTA/course/blob/master/src/Course/Bind.hs )
Martin Capodici,

1
questa risposta è la migliore in assoluto
Curycu,

142

Un foo libero sembra essere la cosa più semplice che soddisfa tutte le leggi "foo". Vale a dire che soddisfa esattamente le leggi necessarie per essere un pazzo e niente di più.

Un funzione smemorato è uno che "dimentica" parte della struttura mentre passa da una categoria all'altra.

Dati funzioni F : D -> C, e G : C -> D, diciamo F -| G, Fè lasciato aggiunto G, o Gè aggiunto a destra Fogni volta che tutto a, b: F a -> bè isomorfo a -> G b, dove le frecce provengono dalle categorie appropriate.

Formalmente, un funzione libera viene aggiunta a un funzione smemorato.

Il monoide libero

Cominciamo con un esempio più semplice, il monoide libero.

Prendete un monoide, che è definita da alcuni set vettore T, una funzione binaria per schiacciare una coppia di elementi insieme f :: T → T → T, ed una unit :: T, in modo tale che si dispone di una legge associativa, e una legge di identità: f(unit,x) = x = f(x,unit).

Si può fare un funtore Udalla categoria di monoidi (dove le frecce sono homomorphisms monoid, che è, assicurano mappano unital unitdall'altra monoid, e che è possibile comporre prima o dopo la mappatura all'altra monoide senza cambiare significato) alla categoria di insiemi (in cui le frecce sono solo frecce di funzione) che "dimenticano" l'operazione e unitti danno il set di corriere.

Quindi, è possibile definire un funzione Fdalla categoria di set alla categoria di monoidi che viene aggiunta in aggiunta a questo funzione. Quel functor è il functor che mappa un set asul monoide [a], dove unit = []e mappend = (++).

Quindi, per rivedere il nostro esempio finora, in pseudo-Haskell:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

Quindi per mostrare Fè gratuito, dobbiamo dimostrare che è lasciato adiacente U, un funzione smemorato, cioè, come abbiamo menzionato sopra, dobbiamo mostrare che

F a → b è isomorfo a a → U b

ora, ricorda che l'obiettivo di Fè nella categoria Mondei monoidi, in cui le frecce sono omomorfismi monoidi, quindi abbiamo bisogno di dimostrare che un omomorfismo monoide [a] → bpuò essere descritto precisamente da una funzione di a → b.

In Haskell, noi chiamiamo il lato di questo che vive Set(ehm, Haskla categoria dei tipi di Haskell che pretendiamo sia Set), giusto foldMap, che quando è specializzato dagli Data.FoldableElenchi ha il tipo Monoid m => (a → m) → [a] → m.

Ci sono conseguenze che ne derivano dal fatto che si tratta di un'aggiunta. In particolare, se dimentichi, costruisci con libero, poi dimentica di nuovo, è proprio come hai dimenticato una volta, e possiamo usarlo per costruire l'unione monadica. da UFUF~ U(FUF)~ UF, e possiamo trasmettere l'omomorfismo monoide dell'identità da [a]a [a]attraverso l'isomorfismo che definisce la nostra aggiunta, ottenere che un elenco di isomorfismi [a] → [a]è una funzione di tipo a -> [a], e questo è solo un ritorno per gli elenchi.

Puoi comporre tutto questo più direttamente descrivendo un elenco in questi termini con:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

La monade libera

Quindi cos'è una Monade libera ?

Bene, facciamo la stessa cosa che abbiamo fatto prima, iniziamo con un funzione di dimenticanza U dalla categoria di monadi in cui le frecce sono omomorfismi di monade a una categoria di endofunctor in cui le frecce sono trasformazioni naturali, e cerchiamo un funzione che viene lasciato in aggiunta a tale.

Quindi, in che modo questo si collega alla nozione di monade libera come viene normalmente usata?

Sapere che qualcosa è una monade libera, Free fti dice che dare una monade dall'omomorfismo Free f -> m, è la stessa cosa (da isomorfa a) che dare una trasformazione naturale (un omomorfismo di funzione) da f -> m. Ricorda che F a -> bdeve essere isomorfo a -> U bperché F sia lasciato in aggiunta a U. U qui monade mappato su funzione.

F è almeno isomorfo rispetto al Freetipo che uso nel mio freepacchetto sull'hackage.

Potremmo anche costruirlo in stretta analogia con il codice sopra per l'elenco libero, definendo

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Cofree Comonads

Possiamo costruire qualcosa di simile, osservando l'aggiunta giusta a un funzione smemorato supponendo che esista. Un funzionario cofree è semplicemente / giusto aggiunta / ad un funzione smemorato e, per simmetria, sapere che qualcosa è un comonad di caffè è lo stesso che sapere che dare un omomorfismo comune w -> Cofree fè la stessa cosa che dare una trasformazione naturale da w -> f.


12
@PauloScardine, questo è niente che si deve essere preoccupati. La mia domanda è venuta dall'interesse per comprendere una struttura di dati avanzata e forse avere un'idea di ciò che è all'avanguardia nello sviluppo di Haskell in questo momento - non è affatto necessario o rappresentativo di ciò che sta scrivendo Haskell finora. (E un avvertimento, migliora una volta superata la fase di apprendimento IO)
David

8
@PauloScardine Non hai bisogno della risposta sopra per programmare in modo produttivo in Haskell, anche con monadi libere. In effetti, non consiglierei di attaccare la monade libera in questo modo a qualcuno che non aveva una preparazione teorica di categoria. Esistono molti modi per parlarne da una prospettiva operativa e capire come usarne uno senza immergersi nella teoria delle categorie. Tuttavia, è impossibile per me rispondere alla domanda su da dove provengono senza immergersi nella teoria. Le costruzioni libere sono un potente strumento nella teoria delle categorie, ma non è necessario questo background per usarle.
Edward KMETT

18
@PauloScardine: Non hai bisogno di alcun calcolo per utilizzare Haskell in modo efficace e persino capire cosa stai facendo. È un po 'strano lamentarsi del fatto che "questo linguaggio è math" quando il mathness è solo bontà extra che puoi usare per divertimento e profitto. Non ottieni queste cose nella maggior parte delle lingue imperative. Perché ti lamenti degli extra? Puoi semplicemente scegliere di NON ragionare matematicamente e affrontarlo come faresti con qualsiasi altra nuova lingua.
Sarah,

3
@Sarah: Devo ancora vedere una documentazione o una conversazione IRC su haskell che non è pesante sulla teoria dei computer e sui term di calcolo lambda.
Paulo Scardine,

11
@PauloScardine sta andando alla deriva un po 'OT, ma a difesa di Haskell: cose tecniche simili si applicano a tutti gli altri linguaggi di programmazione, solo che Haskell ha una compilation così bella che la gente può davvero divertirsi a parlarne. Perché / come X è una monade è interessante per molte persone, le discussioni sullo standard IEEE in virgola mobile non lo sono; entrambi i casi non contano per la maggior parte delle persone, perché possono semplicemente usare i risultati.
David,

72

La Monade libera (struttura dei dati) è alla Monade (classe) come l'Elenco (struttura dei dati) al Monoid (classe): è l'implementazione banale, dove puoi decidere in seguito come combinare il contenuto.


Probabilmente sai cos'è una Monade e che ogni Monade ha bisogno di un'implementazione specifica (rispettosa della legge Monade) di fmap+ join+ returno bind+ return.

Supponiamo che tu abbia un Functor (un'implementazione di fmap) ma il resto dipende dai valori e dalle scelte fatte in fase di esecuzione, il che significa che vuoi essere in grado di utilizzare le proprietà Monad ma vuoi scegliere le funzioni Monad in seguito.

Ciò può essere fatto usando la Monade libera (struttura dei dati), che avvolge il Functor (tipo) in modo tale che si jointratti piuttosto di un accatastamento di quei funzioni piuttosto che di una riduzione.

Il reale returne che joinsi desidera utilizzare ora possono essere indicati come parametri della funzione di riduzione foldFree:

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

Per spiegare i tipi, possiamo sostituire Functor fcon Monad me bcon (m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

8
Questa risposta mi ha dato l'impressione di capire a cosa potrebbero essere utili.
David,

59

Una monade libera di Haskell è un elenco di funzioni. Confrontare:

data List a   = Nil    | Cons  a (List a  )

data Free f r = Pure r | Free (f (Free f r))

Pureè analogo a Niled Freeè analogo a Cons. Una monade libera memorizza un elenco di funzioni anziché un elenco di valori. Tecnicamente, potresti implementare monadi libere usando un diverso tipo di dati, ma qualsiasi implementazione dovrebbe essere isomorfa a quella sopra.

Usi monadi gratuite ogni volta che hai bisogno di un albero di sintassi astratto. Il funzione base della monade libera è la forma di ogni passo dell'albero della sintassi.

Il mio post , che qualcuno ha già collegato, fornisce diversi esempi su come costruire alberi di sintassi astratti con monadi libere


6
So che stavi solo disegnando un'analogia piuttosto che fare una definizione, ma una monade libera non è certamente analoga a un elenco di funzioni in alcun senso. È molto più vicino a un albero di funzioni.
Tom Ellis,

6
Rispetto la mia terminologia. Ad esempio, usando il mio pacchetto index-core è possibile definire "comprensioni di monade libere", che si comportano proprio come la monade dell'elenco, tranne per il fatto che si associano i funzioni invece dei valori. Una monade libera è un elenco di funzioni, nel senso che se traduci tutti i concetti di Haskell nella categoria di funzioni, le liste diventano monadi libere. Un vero albero di funzioni diventa quindi qualcosa di completamente diverso.
Gabriel Gonzalez,

4
Hai ragione sul fatto che la monade è la categorizzazione, in un certo senso, del concetto di monoide, quindi le monadi libere sono analoghe ai monoidi liberi, cioè agli elenchi. In tal senso hai certamente ragione. Tuttavia, la struttura di un valore di una monade libera non è un elenco. È un albero, come dettagliato di seguito .
Tom Ellis,

2
@TomEllis Tecnicamente, è solo un albero se il tuo funzione di base è un prodotto funzione. Quando si dispone di una funzione di somma come funzione di base, essa assomiglia più da vicino a una pila.
Gabriel Gonzalez,

21

Penso che un semplice esempio concreto possa essere d'aiuto. Supponiamo di avere un funzione

data F a = One a | Two a a | Two' a a | Three Int a a a

con l'ovvio fmap. Poi Free F aè il tipo di alberi le cui foglie hanno il tipo ae le cui nodi sono etichettati con One, Two, Two'e Three. One-nodi hanno un figlio, Twoe Two'-nodi hanno due figli e Three-nodi ne hanno tre e sono anche etichettati con un Int.

Free Fè una monade. returnè mappato xall'albero che è solo una foglia con valore x. t >>= fguarda ciascuna delle foglie e le sostituisce con alberi. Quando la foglia ha valore y, sostituisce quella foglia con l'albero f y.

Un diagramma lo rende più chiaro, ma non ho le strutture per disegnarne facilmente uno!


14
Quello che state dicendo è che la monade libera prende la forma del funzione stessa. Quindi se il functor è simile ad un albero (prodotti), la monade libera è simile ad un albero; se è simile a una lista (somme), la monade libera è simile a una lista; se è simile a una funzione, la monade libera è simile a una funzione; ecc. Questo ha senso per me. Quindi, proprio come in un monoide gratuito, continui a trattare ogni applicazione di mappend come creando un elemento completamente nuovo; nella monade libera, trattate ogni applicazione del functor come un elemento completamente nuovo.
Bartosz Milewski,

4
Anche se il funzione è un "somma funzione", la monade libera risultante è ancora simile ad un albero. Si finisce con più di un tipo di nodo nella struttura: uno per ogni componente della somma. Se il tuo "somma funzione" è X -> 1 + X, allora effettivamente otterrai un elenco, che è solo una sorta di albero degenerato.
Tom Ellis,
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.