2012-03-22 22 views
10

Pensé que, en principio, el sistema de tipo de haskell prohibiría llamar a funciones impuras (es decir, f :: a -> IO b) de las puras, pero hoy me di cuenta de que al llamarlas con return compilan muy bien. En el ejemplo:¿Cuál es el significado de las acciones de IO dentro de funciones puras?

h :: Maybe() 
h = do 
    return $ putStrLn "???" 
    return() 

Ahora, h obras en la mónada tal vez, pero es una pura función, sin embargo. Compilarlo y ejecutarlo simplemente devuelve Just() como uno esperaría, sin hacer ninguna E/S. Creo que la pereza de Haskell junta las cosas (es decir, el valor de retorno de putStrLn no se usa, y no puede, ya que sus constructores de valores están ocultos y no puedo hacer coincidir el patrón), pero ¿por qué es legal este código? ¿Hay alguna otra razón que hace esto permitido?

Como extra, pregunta relacionada: en general, ¿es posible prohibir en absoluto la ejecución de acciones de una mónada desde otras? ¿Cómo?

Respuesta

19

Las acciones de IO son valores de primera clase como cualquier otro; eso es lo que hace que el IO de Haskell sea tan expresivo, lo que le permite construir estructuras de control de orden superior (como mapM_) desde cero. La pereza no es relevante aquí, es solo que en realidad no está ejecutando la acción. Solo está construyendo el valor Just (putStrLn "???"), y luego tirándolo.

putStrLn "???"existente no hace que se imprima una línea en la pantalla. Por sí solo, putStrLn "???" es solo una descripción de algunos IO que podrían hacerse para causar que se imprima una línea en la pantalla. La única ejecución que ocurre es ejecutar main, que construyó a partir de otras acciones IO, o cualquier acción que escriba en GHCi. Para obtener más información, consulte el introduction to IO.

De hecho, es perfectamente concebible que desee hacer malabares con IO acciones dentro de Maybe; imagine una función String -> Maybe (IO()), que verifica la validez de la cadena y, si es válida, devuelve una acción IO para imprimir cierta información derivada de la cadena. Esto es posible precisamente debido a las acciones de IO de primera clase de Haskell.

Pero una mónada no tiene la capacidad de ejecutar las acciones de otra mónada a menos que le des esa habilidad.

De hecho, h = putStrLn "???" `seq` return() no causa ningún IO que se realiza o bien, a pesar de que obliga a la evaluación de putStrLn "???".

+0

¿Cómo puedo darle a una mónada la capacidad de ejecutar acciones desde otra, dándole la posibilidad de emparejar patrones con los valores que contiene? –

+3

Escribiendo métodos que convierten una mónada en otra, o realizan alguna ejecución. 'Control.Monad.ST.stToIO' convierte un cálculo' ST' en un cómputo 'IO', por ejemplo. –

+0

Cristalino. ¡Gracias a los dos! –

4

Let's desugar!

h = do return (putStrLn "???"); return() 
-- rewrite (do foo; bar) as (foo >> do bar) 
h = return (putStrLn "???") >> do return() 
-- redundant do 
h = return (putStrLn "???") >> return() 
-- return for Maybe = Just 
h = Just (putStrLn "???") >> Just() 
-- replace (foo >> bar) with its definition, (foo >>= (\_ -> bar)) 
h = Just (putStrLn "???") >>= (\_ -> Just()) 

Ahora, lo que sucede cuando se evalúa h? * Bueno, para lo mejor,

(Just x) >>= f = f x 
Nothing >>= f = Nothing 

Así que el patrón coincide con el primer caso

f x 
-- x = (putStrLn "???"), f = (\_ -> Just()) 
(\_ -> Just()) (putStrLn "???") 
-- apply the argument and ignore it 
Just() 

Note como nunca tuvimos que realizar putStrLn "???" para evaluar esta expresión.

* n.b. No está claro en qué punto "desugaring" se detiene y comienza la "evaluación".Depende de las decisiones de elaboración de tu compilador. Los cálculos puros podrían evaluarse completamente en tiempo de compilación.

+1

Gracias por desugaring. Muy útil para un recién llegado. No entiendo por qué tantos tutoriales comienzan con azúcar. Primero cenamos, luego desertamos. – masterxilo

Cuestiones relacionadas