Domanda

Sto imparando a conoscere le monadi e ho alcune domande.

Questo è dove sono adesso. Per favore, correggimi dove sbaglio.

  • Il simbolo >>= è un operatore infix. Gli operatori Infix sono funzioni che accettano due argomenti (lato sinistro e lato destro) e restituiscono un valore.

  • Il simbolo Monad m => m t -> (t -> m u) -> m u è chiamato operatore di bind e ha la firma m t. Tuttavia, i tipi non sembrano allinearsi qui. Otteniamo un valore di tipo t e il secondo argomento è una funzione che accetta un m. (Non vedo come collegare i punti.)

  • Ciò deve significare che la funzione di associazione è in qualche modo in grado di rimuovere <=> da <=> per ottenere <=> e passarlo alla funzione.

Ecco le mie domande:

  • È la possibilità di rimuovere <=> da <=> qualcosa che è possibile solo all'interno di un tale operatore di bind. Questo operatore di bind ha dei privilegi speciali o qualcosa del genere?

  • Che cosa ha a che fare con i cambiamenti di stato? Capisco (penso) che l'obiettivo delle monadi sia "avvolgere" gli effetti collaterali in modo che siano isolati dal resto del programma. Ma qual è il ruolo dell'operatore di bind in questo?

È stato utile?

Soluzione

  

La possibilità di rimuovere la 'M' da 'M t' è qualcosa che è possibile solo all'interno di un tale operatore di bind. Questo operatore di bind ha dei privilegi speciali o qualcosa del genere?

Il legame non è in alcun modo un caso speciale, ma di solito verrà definito nello stesso modulo del tipo di dati monads. Pertanto potrebbe conoscere (e utilizzare) i dettagli che non vengono esportati dal modulo. Il solito caso sarebbe che il modulo esporti un tipo di dati, ma non i costruttori o altri dettagli sulla struttura interna dei tipi. Quindi, per il codice che utilizza il modulo, il funzionamento interno del tipo di dati è invisibile e quel codice non può modificare direttamente i valori di questo tipo.

Opposto a quelle funzioni definite all'interno del modulo, come ad esempio alcuni operatori di bind >>=, possono accedere a ciò che vogliono dal modulo in cui sono definite. Quindi tali funzioni potrebbero essere in grado di fare le cose " esterno " le funzioni non possono farlo.

Un caso speciale è la IO monade, poiché non è definita da un modulo, ma integrata nel sistema / compilatore di runtime. Qui il compilatore conosce i dettagli interni della sua implementazione ed espone funzioni come <=> s <=>. Le implementazioni di queste funzioni sono infatti particolarmente privilegiate poiché vivono & Quot; al di fuori del programma & Quot ;, ma questo è un caso speciale e questo fatto non dovrebbe essere osservabile all'interno di Haskell.

  

Che cosa ha a che fare con i cambiamenti di stato? Capisco (penso) che l'obiettivo delle monadi sia "avvolgere" gli effetti collaterali in modo che siano isolati dal resto del programma. Ma qual è il ruolo dell'operatore di bind in questo?

Non ha davvero a che fare con i cambiamenti di stato, questo è solo un problema che può essere gestito con moands. La <=> monade viene utilizzata per eseguire IO in un determinato ordine, ma in genere le monadi sono solo modi di combinare funzioni insieme.

Generalmente una monade (in particolare la sua funzione bind) definisce un modo in cui determinate funzioni dovrebbero essere composte insieme a funzioni più grandi. Questo metodo di combinazione delle funzioni è astratto nella monade. Come funziona esattamente questa combinazione o perché vorresti combinare le funzioni in questo modo non è importante, una monade specifica solo un modo di combinare determinate funzioni in un certo modo. (Vedi anche this & Quot; Monadi per C # programmatori " rispondi dove praticamente lo ripeto alcune volte con esempi.)

Altri suggerimenti

  

è la capacità di rimuovere la 'M' da 'M t' qualcosa che è possibile solo all'interno di un tale operatore di bind.

Bene, è certamente possibile all'interno dell'operatore bind, come specifica il suo tipo:

(>>=) :: m a -> (a -> m b) -> m b

La funzione 'run' per la tua monade di solito può fare anche questo (per restituire un valore puro dal tuo calcolo).

  

l'obiettivo delle monadi è "avvolgere" gli effetti collaterali in modo che siano isolati dal resto del programma

Hmm. No, le monadi ci permettono di modellare le nozioni di calcolo. I calcoli con effetti collaterali sono solo una di queste nozioni, come stato, backtracking, continuazioni, concorrenza, transazioni, risultati opzionali, risultati casuali, stato riverificabile, non determinismo ... tutto ciò può essere descritto come una monade

Presumo che la monade IO sia a cui ti riferisci. È una monade leggermente strana: genera sequenze di cambiamenti astratti allo stato del mondo, che vengono quindi valutati dal runtime. Bind ci consente semplicemente di mettere in sequenza le cose nel giusto ordine nella monade IO - e il compilatore tradurrà quindi tutte queste azioni sequenziali di modifica del mondo in codice imperativo che cambia lo stato della macchina.

Questo è molto specifico per la monade IO, non per le monadi in generale.

La seguente è la definizione della classe di tipo Monad.

class  Monad m  where

    (>>=)       :: forall a b. m a -> (a -> m b) -> m b
    (>>)        :: forall a b. m a -> m b -> m b
    return      :: a -> m a
    fail        :: String -> m a

    m >> k      = m >>= \_ -> k
    fail s      = error s

Ogni istanza di tipo della classe di tipo >>= definisce la propria funzione Maybe. Ecco un esempio dall'istanza di tipo data Maybe a:

instance  Monad Maybe  where

    (Just x) >>= k      = k x
    Nothing  >>= _      = Nothing

    (Just _) >>  k      = k
    Nothing  >>  _      = Nothing

    return              = Just
    fail _              = Nothing

Come possiamo vedere, perché la Nothing versione di Just a è appositamente definita per comprendere l'istanza di tipo a e perché è definita in un luogo che ha accesso legale ai Maybe a costruttori di dati <=> e <=>, la <=> versione di <=> è in grado di scartare i <=> in <=> e passarli attraverso.

Per elaborare un esempio, potremmo prendere:

x :: Maybe Integer
x = do a <- Just 5
       b <- Just (a + 1)
       return b

Eliminato, la notazione diventa:

x :: Maybe Integer
x = Just 5        >>= \a ->
    Just (a + 1)  >>= \b ->
    Just b

Che valuta come:

  =                  (\a ->
    Just (a + 1)  >>= \b ->
    Just b) 5

  = Just (5 + 1)  >>= \b ->
    Just b

  =                  (\b ->
    Just b) (5 + 1)

  = Just (5 + 1)

  = Just 6

I tipi si allineano, abbastanza stranamente. Ecco come.

Ricorda che una monade è anche una funzione. La seguente funzione è definita per tutti i funzione:

fmap :: (Functor f) => (a -> b) -> f a -> f b

Ora la domanda: questi tipi si allineano davvero? Beh si. Data una funzione da a a b, quindi se abbiamo un ambiente f in cui m è disponibile, abbiamo un ambiente m a in cui m (m a) è disponibile.

Per analogia al sillogismo:

(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal

Ora, come sai, una monade è una funzione dotata di bind e return:

return :: (Monad m) => a -> m a
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b

Potresti non saperlo in modo equivalente, è un funzione dotato di return e join:

join :: (Monad m) => m (m a) -> m a

Guarda come stiamo staccando un (=<<). Con una monade (a -> m b), non puoi sempre andare da fmap a m a -> m (m b), ma puoi sempre andare da a -> m b a m (m b).

Ora guarda il primo argomento su join. È una funzione di tipo <=>. Cosa succede quando si passa quella funzione a <=>? Ottieni <=>. Quindi, & Quot; mapping & Quot; oltre un <=> con una funzione <=> ti dà <=>. Si noti che questo è esattamente come il tipo dell'argomento su <=>. Questa non è una coincidenza. Una ragionevole implementazione di & Quot; bind & Quot; assomiglia a questo:

(>>=) :: m a -> (a -> m b) -> m b
x >>= f = join (fmap f x)

In effetti, bind e join possono essere definiti l'uno nell'altro:

join = (>>= id)

Consiglio MOLTO MOLTO di leggere ( http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html ). Fornisce una ragione perfetta e di buon senso per cui esistono le monadi.

  

Capisco (penso) che l'obiettivo delle monadi sia "avvolgere" gli effetti collaterali in modo che siano isolati dal resto del programma.

In realtà è un po 'più sottile di così. Le monadi ci consentono di modellare il sequenziamento in modo molto generale. Spesso quando parli con un esperto di dominio li trovi che dicono qualcosa come & Quot; prima proviamo X. Quindi proviamo Y, e se ciò non funziona allora proviamo Z & Quot ;. Quando si arriva a implementare qualcosa del genere in un linguaggio convenzionale, si scopre che non si adatta, quindi è necessario scrivere un sacco di codice aggiuntivo per coprire qualunque cosa l'esperto di dominio intendesse con la parola & Quot; quindi & Quot ;.

In Haskell puoi implementarlo come monade con " quindi " tradotto nell'operatore di bind. Quindi, per esempio, una volta ho scritto un programma in cui un oggetto doveva essere assegnato dai pool secondo determinate regole. Per il caso 1 l'hai preso dal pool X. Se quello era vuoto, sei passato al pool Y. Per il caso 2 hai dovuto prenderlo direttamente dal pool Y. E così via per una dozzina di casi, inclusi alcuni in cui hai scattato il meno usato di recente dal pool X o Y. Ho scritto una monade personalizzata appositamente per quel lavoro in modo da poter scrivere:

case c of
   1: do {try poolX; try poolY}
   2: try poolY
   3: try $ lru [poolX, poolY]

Ha funzionato molto bene.

Naturalmente questo include modelli convenzionali di sequenziamento. La monade IO è il modello che hanno tutti gli altri linguaggi di programmazione; è solo che in Haskell è una scelta esplicita piuttosto che parte dell'ambiente. La monade ST fornisce la mutazione della memoria di IO, ma senza input e output effettivi. D'altra parte la monade dello stato ti consente di limitare il tuo stato a un singolo valore di un tipo denominato.

Per qualcosa che si pieghi davvero al cervello, vedi questo post sul blog su una monade di stato arretrata. Lo stato si propaga nella direzione opposta a & Quot; esecuzione & Quot ;. Se pensi a questo come a una monade di stato che esegue un'istruzione seguita dalla successiva, allora un & Quot; put & Quot; invierà il valore dello stato indietro nel tempo a qualsiasi " precedente; get " ;. Ciò che effettivamente accade è che viene impostata una funzione reciprocamente ricorsiva che termina solo se non ci sono paradossi. Non sono sicuro di dove usare una tale monade, ma illustra il punto in cui le monadi sono modelli di calcolo.

Se non sei pronto per questo, allora pensa a bind come un punto e virgola sovraccaricabile. Questo ti fa fare molta strada.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top