2016-02-14 19 views
8

Sono un neofita della programmazione funzionale (proveniente da javascript), e ho difficoltà a capire la differenza tra i due, che è anche un po 'incasinato con la mia comprensione dei funtori contro le monadi.Differenza nella capacità tra fmap e bind?

Functor:

class Functor f where 
    fmap :: (a -> b) -> f a -> f b 

Monade (semplificato):

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
  • fmap prende una funzione e un funtore, e restituisce un funtore.
  • >>= prende una funzione e una monade e restituisce una monade.

La differenza tra i due è nel parametro di funzione:

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>= prende un parametro di funzione che restituisce una monade. So che questo è significativo, ma ho difficoltà a vedere come questa piccola cosa rende le monadi molto più potenti dei funtori. Qualcuno può spiegare?

+4

questo è più facile visto con la versione capovolta di '(>> =)', ['(= <<)'] (https://stackoverflow.com/questions/34545818/is-monad-bind-operator- più vicino-a-funzione-composizione-chaining-o-functi/34561605 # 34561605). Con '(g <$>) :: f a -> f b', la funzione' g :: a -> b' non ha influenza sul 'f'" involucro "- non lo cambia. Con '(k = <<) :: m a -> mb', la funzione' k :: a -> mb' stesso * crea * il nuovo 'm'" involucro ", quindi può cambiare. –

+0

@WillNess Posso" capire "questo, ma io non riesco a vederlo. Penso che il vero problema che ho è che non riesco a vedere cosa '' >> = 'può fare che' fmap' non può fare. Nella mia testa sono equivalenti perché non ho visto un esempio, che mostra che fmap è inadeguato – m0meni

+4

andando con le liste, prova a filtrare alcuni elementi da un elenco, usando 'map'. non puoi, ma con' concatMap', puoi: 'map (\ x- > x + 1) [1,2,3] 'vs' concatMap (\ x-> [x, x + 1 | even x]) [1,2,3]) '. –

risposta

13

Beh, (<$>) è un alias per fmap, e è lo stesso (>>=) con gli argomenti scambiati:

(<$>) :: (x -> y) -> b x -> b y 
(=<<) :: (x -> b y) -> b x -> b y 

La differenza è ormai abbastanza chiaro: con la funzione bind, applichiamo una funzione che restituisce a b y anziché a . Quindi, che differenza fa?

considerare questo piccolo esempio:

foo <$> Just 3 

noti che (<$>) si applicherà foo-3, e mettere il risultato di nuovo in un Just. In altre parole, il risultato di questo calcolo non può essereNothing. Al contrario:

bar =<< Just 3 

Questo calcolo può ritorno Nothing. (Per esempio, bar x = Nothing lo farà.)

Possiamo fare una cosa simile con la lista monade:

foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list. 
bar =<< [Red, Yellow, Blue] -- Result can be ANY size. 

In breve, con (<$>) (cioè, fmap), la "struttura" del risultato è sempre identico all'input.Ma con (vale a dire, (>>=)), la struttura del risultato può cambiare. Ciò consente l'esecuzione condizionale, la reazione all'input e un sacco di altre cose.

+4

solo per completezza, Applicativo può restituire 'Niente' troppo:' Niente <*> Solo 3'. La differenza è che la "piping" (cioè la struttura computazionale) è * fissa * quando il calcolo è composto, * prima * it * "corre" *. Ma con Monads le pipe possono cambiare a seconda dei valori prodotti * mentre * "corre". (in caso di IO, '3' è presumibilmente ricevuto per esempio come input dell'utente). - L'esempio * list * è esp. bene qui: '((<$>)' mantiene la struttura (lunghezza lista); '([baz, quux] <*>)' cambierà la struttura * in modo prevedibile * (crea un elenco di lunghezza 6); con Monad tutte le scommesse sono spente. –

8

Risposta breve è che se è possibile trasformare m (m a) in m a in un modo che ha senso, quindi è un Monade. Questo è possibile per tutte le Monadi ma non necessariamente per i Functional.

Credo che la cosa più confusione è che tutti gli esempi comuni di Funtori (ad esempio List, Maybe, IO) sono anche Monadi. Abbiamo bisogno di un esempio di qualcosa che sia un Functor ma non una Monade.

Userò un esempio da un ipotetico programma di calendario. Il codice seguente definisce un Functional Event che memorizza alcuni dati associati all'evento e al momento in cui si verifica.

import Data.Time.LocalTime 

data Event a = MkEvent LocalTime a 

instance Functor Event where 
    fmap f (MkEvent time a) = MkEvent time (f a) 

I Event negozi oggetto la volta che si verifica l'evento e un po 'di dati in più che può essere modificato utilizzando fmap. Ora proviamo e ne fanno un Monade:

instance Monad Event where 
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in 
           MkEvent <notSureWhatToPutHere> b 

Troviamo che non possiamo, perché si finisce con due LocalTime oggetti. timeA dal numero Event e timeB dal Event dato dal risultato di f a. Il nostro tipo Event è definito come dotato di un solo LocalTime (time) a cui si verifica e pertanto non è possibile renderlo un Monad senza trasformare due s LocalTime in uno. (Potrebbe esserci qualche caso in cui farlo avrebbe senso e quindi potresti trasformarlo in una monade se lo volessi davvero).

+3

Un esempio di un functor classico/comune che non è una monade è 'newtype Const a b = Const a'. – dfeuer

+3

'pure x >> = f' è richiesto da una legge monad per essere' f x', ma 'pure :: b -> Const a b' non può usare il suo argomento. – dfeuer

+1

@dfeuer Questo [troppo semplice per essere semplice] (https://ncatlab.org/nlab/show/too+simple+to+be+simple). Inoltre non riesco a trovare un modo per scrivere un'istanza di Functor diversa da 'fmap f (Const x) = Const x' – HEGX64

3

Si supponga per un momento che IO fosse solo un Functor e non un Monad. Come possiamo sequenziare due azioni? Dì, come getChar :: IO Char e putChar :: Char -> IO().

Si potrebbe provare a mappare su getChar (un'azione che, una volta eseguita, legge uno Char da stdin) utilizzando putChar.

fmap putChar getChar :: IO (IO()) 

Ora abbiamo un programma che, una volta eseguito, si legge in una Char dallo standard input e produce un programma che, quando eseguito, scrive il Char stdout. Ma quello che vogliamo veramente è un programma che, una volta eseguito, legge uno Char da stdin e scrive lo Char sullo stdout. Quindi abbiamo bisogno di un "appiattimento" (nel caso IO, "sequencing"), funzione con Tipo:

join :: IO (IO()) -> IO() 

Functor di per sé non fornisce questa funzione. Ma si tratta di una funzione del Monad, dove si ha il tipo più generale:

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

Che cosa significa tutto questo ha a che fare con >>=? Come accade, bind monadico è solo una combinazione di fmap e join:

:t \m f -> join (fmap f m) 
(Monad m) => m a1 -> (a1 -> m a) -> m a 

Un altro modo di vedere la differenza è che fmap non cambia la struttura complessiva del valore mappato, ma join (e quindi >>= pure) può farlo

In termini di IO azioni, fmap sarà mai causa Importanti letture/scritture o altri effetti. Ma join sequenze di lettura/scrittura dell'azione interna dopo quelle dell'azione esterna.

Problemi correlati