2011-05-24 19 views
5

En Haskell me puede definir fácilmente una función recursiva que toma un valor y devuelve una cadena:recursiva IO en Haskell

Prelude> let countdown i = if (i > 0) then (show i) ++ countdown (i-1) else "" 
Prelude> countdown 5 
"54321" 

Quiero usar el mismo tipo de diseño para leer los datos disponibles de un identificador de archivo. En este caso particular, necesito leer los datos de la misma manera que hGetContents, pero sin dejar el identificador en el estado "semicerrado", para que pueda iterar la interacción con los controladores stdin/stdout de un proceso abierto con createProcess:

main = do 
    -- do work to get hin/hout handles for subprocess input/output 

    hPutStrLn hin "whats up?" 

    -- works 
    -- putStrLn =<< hGetContents hout 

    putStrLn =<< hGetLines hout 

    where 
     hGetLines h = do 
      readable <- hIsReadable h 
      if readable 
       then hGetLine h ++ hGetLines h 
       else [] 

da el error:

Couldn't match expected type `IO b0' with actual type `[a0]' 
In the expression: hGetLine h : hGetLines h 

sé que hay varias bibliotecas disponibles para llevar a cabo lo que estoy tratando de lograr, pero SICE estoy aprendiendo mi pregunta es realmente cómo realizar recursiva IO. TIA!

Respuesta

10

solución Naive, estricto y O (n) pila

Usted todavía tiene que utilizar el hacer -notation, lo que llevaría a esto:

import System.IO 
import System.IO.Unsafe (unsafeInterleaveIO) 

-- Too strict! 
hGetLines :: Handle -> IO [String] 
hGetLines h = do 
    readable <- hIsReadable h 
    if readable 
     then do 
      x <- hGetLine h 
      xs <- hGetLines h 
      return (x:xs) 
     else return [] 

Pero ver mi ¡Comenta, esta versión de hGetLines es demasiado estricta!

perezoso, streaming versión

No volverá a su lista, hasta que tenga toda la entrada. Necesitas algo un poco más flojo.Para esto, tenemos unsafeInterleaveIO,

-- Just right 
hGetLines' :: Handle -> IO [String] 
hGetLines' h = unsafeInterleaveIO $ do 
    readable <- hIsReadable h 
    if readable 
     then do 
      x <- hGetLine h 
      xs <- hGetLines' h 
      return (x:xs) 
     else return [] 

ya se puede empezar a emitir resultados línea por línea en el código del consumidor:

*Main> hGetLines' stdin 
123 
["123"345 
,"345"321 
,"321"^D^CInterrupted. 
-1

Esto está diciendo que parte del código espera que hGetLines h tenga el tipo IO a y que otra parte tenga el tipo [a]. Es probable que su sentencia if para ser:

if readable 
    then return hGetLine h ++ hGetLines h 
    else return [] 
+2

Su código es algo extraño ... No incluso compilar . ¿Qué tal esto: 'si es legible entonces hGetLine >> = \ a -> hGetLine >> = \ b -> return $ a + b else return []'? Otro problema es que esto no se transmite. – fuz

6

Si comprueba el tipo de (++) en ghci que se obtiene:

Prelude> :t (++) 
(++) :: [a] -> [a] -> [a] 

Lo que significa que sólo se puede añadir listas juntas (Recuerde que String es un alias para [Char], entonces es una lista). El tipo de hGetLine es Handle -> IO String, y el tipo de hGetLines debe ser IO [String]. Por lo tanto, no puede anexar estos valores. (:) tiene tipo a -> [a] y funciona mejor aquí.

if readable 
    then do 
    -- First you need to extract them 
    a <- hGetLine h 
    b <- hGetLines h 
    -- a and b have type String 
    -- Now we can cons them and then go back into IO 
    return (a : b) 

Lo mismo aplica para else []. Necesita un valor de tipo IO [String] para devolver. Cambiarlo a return []

Además, usted no será capaz de putStrLn sólo las líneas ya (=<< hGetLines h) le da [String] y no String que es lo que putStrLn Espera. Esto se puede resolver de varias maneras. Una es concaturar los valores primero. putStrln . concat =<< (hGetLines h). O puede imprimir cada línea usando mapM_ putStrLn (hGetLines h).

+0

¿Querías llamar a 'hGetLines' en la segunda llamada? –

+0

Woops. Se perdió la llamada recursiva, por lo que ':' debería usarse en su lugar. –

+0

Tenga en cuenta que este ejemplo no se transmitirá y utiliza la pila * O (n) *. –