2011-10-17 14 views
7

Mientras juego con mónadas a menudo incurro en problemas de evaluación. Ahora, entiendo los conceptos básicos de evaluación perezosa, pero no entiendo cómo las mónadas se evalúan perezosamente en Haskell.evaluación de Haskell y perezosas Mónadas

Considere el siguiente código

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

main = print <$> head <$> getArgs 

En mi mente se debe a la función principal debe imprimir el primer argumento de la consola, pero no es así.

sé que

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

lo que al parecer, el primer argumento no se imprime en la salida estándar debido a que el contenido de la primera mónada IO no se evalúa. Entonces, si me uno a las dos mónadas, funciona.

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

¿Alguien podría aclararlo por mí? (O me da un puntero)

Respuesta

11

Haskell Informe 2010 (la definición del lenguaje) says:

El valor del programa es el valor del identificador main en el módulo Main, que debe ser un cálculo de tipo IO τ para algún tipo τ. Cuando se ejecuta el programa, el cálculo main es realizado, y su resultado (del tipo τ) se descarta.

Su función es de tipo mainIO (IO()). La cita anterior significa que solo se evalúa la acción externa (IO (IO())) y se descarta su resultado (IO()). ¿Cómo has llegado hasta aquí? Veamos el tipo de print <$>:

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

Así que el problema es que utilizó fmap conjuntamente con print. En cuanto a la definición de Functor instancia para IO:

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

se puede ver que hizo que su expresión equivalente a (head <$> getArgs >>= return . print). Para hacer lo que ha sido fabricado, basta con retirar el innecesaria return:

head <$> getArgs >>= print 

O, lo que es equivalente:

print =<< head <$> getArgs 

Tenga en cuenta que las acciones IO en Haskell son al igual que otros valores - que se pueden pasar a y devueltos desde funciones, almacenadas en listas y otras estructuras de datos, etc. Una acción IO no se evalúa a menos que sea parte del cálculo principal. Para "pegar" acciones IO juntas, use >> y >>=, no fmap (que se usa generalmente para mapear funciones puras sobre los valores en alguna "caja" - en su caso, IO).

Tenga en cuenta también que esto no tiene que ver con la evaluación diferida, sino con la pureza: semánticamente, su programa es una función pura que devuelve un valor de tipo IO a, que luego interpreta el sistema de tiempo de ejecución. Como su acción interna IO no es parte de este cálculo, el sistema de tiempo de ejecución simplemente lo descarta. Una buena introducción a estos temas es el segundo capítulo de "Tackling the Awkward Squad" de Simon Peyton Jones.

+0

Muchas gracias por la respuesta exhaustiva. – Jack

+0

Así que, básicamente, lo que hice fue ejecutar getArgs (el IO externo) y arrojar los resultados en algo que nunca se ejecutó (porque lo ignoró el principal). – Jack

+1

Correcto. Tu código ejecuta 'head <$> getArgs' y luego envía su resultado a' return. print', que tiene el tipo 'a -> IO (IO())'. –

4

Tiene head <$> getArgs :: IO String y print :: Show a => a -> IO(), es decir, un valor en una mónada y una función desde un valor simple hasta una mónada. La función utilizada para componer tales cosas es el operador monadic bind (>>=) :: Monad m => m a -> (a -> m b) -> m b.

Así que lo que quiere es

main = head <$> getArgs >>= print 

(<$>) aka fmap tiene el tipo Functor f => (a -> b) -> f a -> f b, por lo que es útil cuando se desea aplicar una función puraa algún valor en una mónada, que es por qué funciona con head pero no con print, ya que print no es puro.

Cuestiones relacionadas