2009-04-16 24 views
10

Considere el siguiente programa de Haskell. Estoy intentando programar en un "estilo de secuencia" donde las funciones operan en transmisiones (implementadas aquí simplemente como listas). Cosas como normalStreamFunc funcionan muy bien con listas perezosas. Puedo pasar una lista infinita a normalStreamFunc y sacar efectivamente otra lista infinita, pero con una función asignada a cada valor. Cosas como effectfulStreamFunc no funcionan tan bien. La acción IO significa que necesito evaluar toda la lista antes de poder obtener valores individuales. Por ejemplo, la salida del programa es la siguiente:Haskell arroyos con efectos IO

a 
b 
c 
d 
"[\"a\",\"b\"]" 

pero lo que quiero es una manera de escribir effectfulStreamFunc para que el programa produce esto:

a 
b 
"[\"a\",\"b\"]" 

dejando el resto de acciones sin evaluar. Puedo imaginar una solución que use inseguroPerformIO, pero digamos que lo estoy sacando de la mesa. Este es el programa:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

Actualización:

Gracias a todos por los comentarios útiles y reflexivo. No había visto el operador sequence antes, eso es útil para saber. Yo había pensado en una forma (menos elegante) para pasar alrededor de IO (String) valores en lugar de cadenas, sino por el estilo de programación que es de utilidad limitada, ya que quiero otras funciones de flujo para actuar sobre las cuerdas a sí mismos, no en acciones que pueden producir una cadena. Pero, basado en pensar a través de las otras respuestas, creo que veo por qué esto es irresoluble en general. En el caso simple presenté, lo que realmente quería era el operador sequence, ya que yo estaba pensando que el ordenamiento corriente implicó un ordenamiento de las acciones. De hecho, ningún pedido de este tipo está necesariamente implícito. Esto se vuelve más claro para mí cuando pienso en una función de transmisión que toma dos flujos como entrada (por ejemplo, dos secuencias por pares). Si ambas transmisiones "entrantes" realizan IO, el orden de esas acciones IO no está definido (a menos que, por supuesto, lo definamos mediante secuenciación nosotros mismos en la mónada IO). Problema resuelto, ¡gracias a todos!

Respuesta

7

¿Qué tal este código:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> [IO (String)] 
effectfulStreamFunc [] = [] 
effectfulStreamFunc (x:xs) = 
    let rest = effectfulStreamFunc xs in 
     (putStrLn x >> return x) : rest 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    let appliedFns = effectfulStreamFunc fns 
    pieces <- sequence $ take 2 appliedFns 
    print $ show $ pieces 

En lugar de effectfulStreamFunc realmente hacer cualquier IO, éste en cambio crea una lista de acciones para llevar a cabo IO. (Tenga en cuenta el cambio de tipo de firma.) La función principal a continuación, toma 2 de esas acciones, los ejecuta e imprime los resultados:

a 
b 
"[\"a\",\"b\"]" 

Esto funciona porque el tipo IO (String) es sólo una función/valor como cualquier otro que se pueda poner en una lista, pase alrededor, etc. Tenga en cuenta que la sintaxis de hacerlo no se produce en "effectfulStreamFunc" - en realidad es una pura función, a pesar de la "IO" en su firma. Solo cuando ejecutamos sequence en aquellos en main, los efectos realmente ocurren.

+1

Si aún así como la notación hacerlo, se podría escribir la segunda parte de effectfulStreamFunc como: effectfulStreamFunc (x: xs) = let putAction = do putStrLn x return x en putAction: effectfulStreamFunc xs Creo que se lee un poco mejor. –

+0

Buen punto. Supongo que la sintaxis do es ortogonal al problema de realmente ejecutar la mónada. –

2

no entienden realmente su objetivo principal, pero el uso de los resultados putStrLn en la evaluación de toda la lista, ya que evaluará el argumento cuando se realiza. Considere

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", undefined,"c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

esto se traduce en "[\" A \ "\ "B \"]", mientras que el uso de la versión putStrLn da lugar a una excepción.

1

Según lo mencionado por TomH, realmente no se puede hacer esto "segura", ya que está rompiendo la transparencia referencial en Haskell.Estás tratando de realizar efectos secundarios perezosamente, pero la cuestión de la pereza es que no estás garantizado en qué orden o si las cosas se evalúan, por lo que en Haskell, cuando le dices que realice un efecto secundario, siempre se realiza, y en el orden exacto especificado. (es decir, en este caso, los efectos secundarios de la llamada recursiva de effectfulStreamFunc antes del return, porque ese era el orden en que se enumeraban). No puede hacer esto de forma perezosa sin utilizar inseguro.

Puede intentar usar algo como unsafeInterleaveIO, que es cómo se implementa el perezoso IO (por ejemplo, hGetContents) en Haskell, pero tiene sus propios problemas; y dijiste que no querías usar cosas "inseguras".

import System.IO.Unsafe (unsafeInterleaveIO) 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = unsafeInterleaveIO $ do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse x : rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

En este caso, la salida se parece a esto

"a 
[\"a\"b 
,\"b\"]"