2011-10-17 14 views
7

Mentre gioco con le monadi, spesso ho problemi di valutazione. Ora capisco i concetti di base della valutazione pigra, ma non capisco come le monadi siano pigramente valutate in Haskell.Valutazione Haskell e Lazy Monads

Si consideri il seguente codice

module Main where 
import Control.Monad 
import Control.Applicative 
import System 

main = print <$> head <$> getArgs 

Nella mia mente si deve la funzione principale dovrebbe stampare il primo argomento della console, ma non è così.

so che

getArgs :: IO [String] 
head <$> getArgs :: IO String 
print <$> (head <$> getArgs) :: IO (IO()) 
main :: IO (IO()) 

così apparentemente, il primo argomento non si stampa sul stdout perché il contenuto della prima Monade IO non viene valutato. Quindi se mi unisco alle due monadi, funziona.

main = join $ print <$> head <$> getArgs 

Qualcuno, per favore, chiarirebbe per me? (O darmi un puntatore)

risposta

11

Haskell relazione 2010 (la definizione del linguaggio) says:

Il valore del programma è il valore dell'identificativo main nel modulo Main, che deve essere un calcolo di digitare IO τ per alcuni tipi τ. Quando il programma viene eseguito, il calcolo main è eseguito e il suo risultato (di tipo τ) viene scartato.

La vostra funzione è di tipo mainIO (IO()). La citazione sopra indica che viene valutata solo l'azione esterna (IO (IO())) e il suo risultato (IO()) viene scartato. Come ci sei arrivato? Diamo un'occhiata al tipo di print <$>:

> :t (print <$>) 
(print <$>) :: (Show a, Functor f) => f a -> f (IO()) 

Quindi il problema è che si è utilizzato in combinazione con fmapprint. Guardando la definizione di Functor istanza per IO:

instance Functor IO where 
    fmap f x = x >>= (return . f) 

si può vedere che che ha fatto la tua espressione equivalente a (head <$> getArgs >>= return . print). Per fare quello che originariamente previsto, è sufficiente rimuovere il superfluo return:

head <$> getArgs >>= print 

o, equivalentemente:

print =<< head <$> getArgs 

Nota che le azioni IO a Haskell sono proprio come gli altri valori - possono essere passati per e restituiti da funzioni, memorizzate in elenchi e altre strutture di dati, ecc. Un'azione IO non viene valutata a meno che non sia una parte del calcolo principale. Per "incollare" le azioni IO insieme, utilizzare >> e >>=, non fmap (che viene in genere utilizzato per mappare le funzioni pure su valori in alcuni "riquadri" - nel proprio caso, IO).

Si noti inoltre che questo non deve essere eseguito con la valutazione lenta, ma con la purezza - semanticamente, il programma è una funzione pura che restituisce un valore di tipo IO a, che viene quindi interpretato dal sistema di runtime. Dal momento che l'azione interna IO non fa parte di questo calcolo, il sistema runtime lo scarta. Una buona introduzione a questi problemi è il secondo capitolo di Simon Peyton Jones "Tackling the Awkward Squad".

+0

Grazie mille per la risposta esauriente. – Jack

+0

Quindi in pratica quello che ho fatto è stato eseguire getArgs (l'IO esterno) e lanciare i risultati in qualcosa che non è mai stato eseguito (perché ignorato dal main). – Jack

+1

corretto. Il codice esegue 'head <$> getArgs' e quindi invia il risultato a' return. print', che ha il tipo 'a -> IO (IO())'. –

4

Hai head <$> getArgs :: IO String e print :: Show a => a -> IO(), vale a dire un valore in una monade e una funzione da un valore normale a una monade. La funzione utilizzata per comporre tali elementi è l'operatore monadic binding (>>=) :: Monad m => m a -> (a -> m b) -> m b.

Quindi, ciò che si vuole è

main = head <$> getArgs >>= print 

(<$>) alias fmap ha il tipo Functor f => (a -> b) -> f a -> f b, quindi è utile quando si vuole applicare una pura funzione di a un valore in una monade, che è il motivo per cui lavora con head ma non con print, dal print non è puro.

Problemi correlati