Haskell Language
monadi
Ricerca…
introduzione
Una monade è un tipo di dati di azioni componibili. Monad
è la classe di costruttori di tipi i cui valori rappresentano tali azioni. Forse IO
è il più riconoscibile uno: un valore di IO a
è una "ricetta per il recupero di un a
valore dal mondo reale".
Diciamo che un costruttore di tipo m
(come []
o Maybe
) forma una monade se esiste instance Monad m
soddisfa determinate leggi sulla composizione delle azioni. Possiamo quindi ragionare su ma
come "azione il cui risultato ha tipo a
".
La forse monade
Maybe
è usato per rappresentare valori probabilmente vuoti - simile a null
in altre lingue. Di solito è usato come tipo di output di funzioni che possono fallire in qualche modo.
Considera la seguente funzione:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Pensa a halve
come un'azione, a seconda di un Int
, che prova a dimezzare il numero intero, in caso contrario se è strano.
Come halve
un intero tre volte?
takeOneEighth :: Int -> Maybe Int -- (after you read the 'do' sub-section:)
takeOneEighth x =
case halve x of -- do {
Nothing -> Nothing
Just oneHalf -> -- oneHalf <- halve x
case halve oneHalf of
Nothing -> Nothing
Just oneQuarter -> -- oneQuarter <- halve oneHalf
case halve oneQuarter of
Nothing -> Nothing -- oneEighth <- halve oneQuarter
Just oneEighth ->
Just oneEighth -- return oneEighth }
-
takeOneEighth
è una sequenza di tre passaggihalve
concatenati. - Se un passo di
halve
fallisce, vogliamo che l'intera composizionetakeOneEighth
per fallire. - Se un passo della
halve
riesce, vogliamo invertire il suo risultato.
instance Monad Maybe where
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= f = Nothing -- infixl 1 >>=
Just x >>= f = Just (f x) -- also, f =<< m = m >>= f
-- return :: a -> Maybe a
return x = Just x
e ora possiamo scrivere:
takeOneEighth :: Int -> Maybe Int
takeOneEighth x = halve x >>= halve >>= halve -- or,
-- return x >>= halve >>= halve >>= halve -- which is parsed as
-- (((return x) >>= halve) >>= halve) >>= halve -- which can also be written as
-- (halve =<<) . (halve =<<) . (halve =<<) $ return x -- or, equivalently, as
-- halve <=< halve <=< halve $ x
La composizione di Kleisli <=<
è definita come (g <=< f) x = g =<< fx
, o equivalentemente come (f >=> g) x = fx >>= g
. Con esso la definizione di cui sopra diventa giusta
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Ci sono tre leggi monad che dovrebbero essere osservate da ogni monade, cioè ogni tipo che è un'istanza della classe di Monad
:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
dove m
è una monade, f
ha tipo a -> mb
e g
ha tipo b -> mc
.
O in modo equivalente, usando l'operatore di composizione >=>
Kleisli sopra definito:
1. return >=> g = g -- do { y <- return x ; g y } == g x
2. f >=> return = f -- do { y <- f x ; return y } == f x
3. (f >=> g) >=> h = f >=> (g >=> h) -- do { z <- do { y <- f x; g y } ; h z }
-- == do { y <- f x ; do { z <- g y; h z } }
Obbedire a queste leggi rende molto più facile ragionare sulla monade, perché garantisce che l'uso di funzioni monadiche e la loro composizione si comporti in modo ragionevole, simile ad altre monadi.
Controlliamo se la Maybe
monade obbedisce alle tre leggi monad.
- La legge di identità sinistra -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- La giusta legge sull'identità -
m >>= return = m
-
Just
costruttore di dati
Just z >>= return
= return z
= Just z
-
Nothing
costruttore di dati
Nothing >>= return
= Nothing
- La legge di associatività -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
costruttore di dati
-- Left-hand side
((Just z) >>= f) >>= g
= f z >>= g
-- Right-hand side
(Just z) >>= (\x -> f x >>= g)
(\x -> f x >>= g) z
= f z >>= g
-
Nothing
costruttore di dati
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO monad
Non c'è modo di ottenere un valore di tipo a
da un'espressione di tipo IO a
e non dovrebbe esserci. Questa è in realtà una grande parte del motivo per cui le monadi sono utilizzate per modellare IO
.
Un'espressione di tipo IO a
può essere pensata come rappresentativa di un'azione che può interagire con il mondo reale e, se eseguita, risulterebbe in qualcosa di tipo a
. Ad esempio, la funzione getLine :: IO String
dal preludio non significa che al di sotto di getLine
ci sia una stringa specifica che posso estrarre, significa che getLine
rappresenta l'azione di ottenere una linea dallo standard input.
Non sorprendentemente, main :: IO ()
poiché un programma Haskell rappresenta un calcolo / azione che interagisce con il mondo reale.
Le cose che puoi fare alle espressioni di tipo IO a
perché IO
è una monade:
Sequenza di due azioni utilizzando
(>>)
per produrre una nuova azione che esegue la prima azione, elimina qualsiasi valore prodotto e quindi esegue la seconda azione.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
A volte non vuoi scartare il valore che è stato prodotto nella prima azione: in realtà ti piacerebbe che venisse inserito in una seconda azione. Per questo, abbiamo
>>=
. PerIO
, ha type(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Prendi un valore normale e convertilo in un'azione che restituisce immediatamente il valore che gli hai dato. Questa funzione è meno ovviamente utile fino a quando iniziare a usare
do
la notazione.-- make an action that just returns 5 return 5
Altro dal Wiki Haskell sulla monade IO qui .
Lista Monad
Le liste formano una monade. Hanno una istanziazione monad equivalente a questo:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Possiamo usarli per emulare il non-determinismo nei nostri calcoli. Quando usiamo xs >>= f
, la funzione f :: a -> [b]
è mappata sulla lista xs
, ottenendo un elenco di liste di risultati di ciascuna applicazione di f
su ciascun elemento di xs
e tutti gli elenchi di i risultati vengono quindi concatenati in una lista di tutti i risultati. Ad esempio, calcoliamo una somma di due numeri non deterministici usando la notazione , la somma rappresentata dall'elenco di somme di tutte le coppie di numeri interi da due elenchi, ciascuna lista che rappresenta tutti i possibili valori di un numero non deterministico:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
O in modo equivalente, utilizzando liftM2
in Control.Monad
:
sumnd = liftM2 (+)
otteniamo:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Monad come sottoclasse di Applicative
A partire da GHC 7.10, Applicative
è una superclasse di Monad
(cioè, ogni tipo che è una Monad
deve essere anche un Applicative
). Tutti i metodi di Applicative
( pure
, <*>
) possono essere implementati in termini di metodi di Monad
( return
, >>=
).
È ovvio che il pure
e il return
servono scopi equivalenti, quindi pure = return
. La definizione di <*>
è troppo chiara:
mf <*> mx = do { f <- mf; x <- mx; return (f x) }
-- = mf >>= (\f -> mx >>= (\x -> return (f x)))
-- = [r | f <- mf, x <- mx, r <- return (f x)] -- with MonadComprehensions
-- = [f x | f <- mf, x <- mx]
Questa funzione è definita come ap
nelle librerie standard.
Quindi se hai già definito un'istanza di Monad
per un tipo, puoi effettivamente ottenere un'istanza di Applicative
per questo "gratuitamente" definendo
instance Applicative < type > where
pure = return
(<*>) = ap
Come con le leggi monadi, queste equivalenze non sono applicate, ma gli sviluppatori dovrebbero assicurarsi di essere sempre rispettate.
Nessun modo generale per estrarre il valore da un calcolo monadico
Puoi racchiudere i valori in azioni e convogliare il risultato di un calcolo in un altro:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Tuttavia, la definizione di una Monade non garantisce l'esistenza di una funzione di tipo Monad m => ma -> a
.
Ciò significa che non c'è, in generale, alcun modo per estrarre un valore da un calcolo (cioè "scartarlo"). Questo è il caso per molte istanze:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Nello specifico, non vi è alcuna funzione IO a -> a
, che spesso confonde i principianti; guarda questo esempio
fare notazione
do
notazione è zucchero sintattico per le monadi. Ecco le regole:
do x <- mx do x <- mx y <- my is equivalent to do y <- my ... ...
do let a = b let a = b in ... is equivalent to do ...
do m m >> ( e is equivalent to e)
do x <- m m >>= (\x -> e is equivalent to e)
do m is equivalent to m
Ad esempio, queste definizioni sono equivalenti:
example :: IO Integer
example =
putStrLn "What's your name?" >> (
getLine >>= (\name ->
putStrLn ("Hello, " ++ name ++ ".") >> (
putStrLn "What should we return?" >> (
getLine >>= (\line ->
let n = (read line :: Integer) in
return (n + n))))))
example :: IO Integer
example = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ ".")
putStrLn "What should we return?"
line <- getLine
let n = (read line :: Integer)
return (n + n)
Definizione di Monad
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
La funzione più importante per gestire le monadi è l' operatore di bind >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Pensare
ma
come "un azione con una
risultato". - Pensa a
a -> mb
come "un'azione (a seconda dia
parametro) con un risultatob
" .
>>=
sequenzia due azioni insieme piping il risultato dalla prima azione al secondo.
L'altra funzione definita da Monad
è:
return :: a -> m a
Il suo nome è sfortunato: questo return
non ha nulla a che fare con la parola chiave return
trovata nei linguaggi di programmazione imperativi.
return x
è l'azione banale che produce x
come risultato. (È banale nel senso seguente :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law