haskell io lazy-sequences

haskell - versión perezosa de mapM



io lazy-sequences (2)

La mónada IO tiene un mecanismo para diferir los efectos. Se llama unsafeInterleaveIO . Puede usarlo para obtener el efecto deseado:

import System.IO.Unsafe lazyMapM :: (a -> IO b) -> [a] -> IO [b] lazyMapM f [] = return [] lazyMapM f (x:xs) = do y <- f x ys <- unsafeInterleaveIO $ lazyMapM f xs return (y:ys)

Así es como se implementa la IO lenta. No es seguro que el orden en que se ejecutarán realmente los efectos sea difícil de predecir y estará determinado por el orden en que se evalúan los elementos de la lista de resultados. Por esta razón, es importante que los efectos IO en f sean benignos, en el sentido de que deben ser insensibles a los pedidos. Un buen ejemplo de un efecto normalmente benigno es leer de un archivo de solo lectura.

Supongamos que recibo una gran lista de elementos mientras trabajo con IO:

as <- getLargeList

Ahora, estoy tratando de aplicar fn :: a -> IO b en:

as <- getLargeList bs <- mapM fn as

mapM tiene el tipo mapM :: Monad m => (a -> mb) -> [a] -> m [b] , y eso es lo que necesito en términos de coincidencia de tipos. Pero construye toda la cadena en la memoria hasta devolver el resultado. Estoy buscando un análogo de mapM , que funcionará perezosamente, para poder usar head of bs mientras sigo construyendo tail.


No use unsafeInterleaveIO o cualquier IO perezosa para ese asunto. Este es precisamente el problema que las iteraciones fueron creadas para abordar: evitar la IO lenta, lo que proporciona una administración de recursos impredecible. El truco es nunca compilar la lista y transmitirla constantemente usando repeticiones hasta que haya terminado de usarla. Usaré ejemplos de mi propia biblioteca, pipes , para demostrar esto.

Primero, defina:

import Control.Monad import Control.Monad.Trans import Control.Pipe -- Demand only ''n'' elements take'' :: (Monad m) => Int -> Pipe a a m () take'' n = replicateM_ n $ do a <- await yield a -- Print all incoming elements printer :: (Show a) => Consumer a IO r printer = forever $ do a <- await lift $ print a

Ahora seamos groseros con nuestro usuario y exijamos que nos generen una lista realmente grande:

prompt100 :: Producer Int IO () prompt100 = replicateM_ 1000 $ do lift $ putStrLn "Enter an integer: " n <- lift readLn yield n

Ahora, vamos a ejecutarlo:

>>> runPipe $ printer <+< take'' 1 <+< prompt100 Enter an integer: 3<Enter> 3

Solo solicita al usuario un entero, ¡ya que solo pedimos un número entero!

Si desea reemplazar prompt100 con salida de getLargeList , solo escriba:

yourProducer :: Producer b IO () yourProducer = do xs <- lift getLargeList mapM_ yield xs

... y luego ejecutar:

>>> runPipe $ printer <+< take'' 1 <+< yourProducer

Esto transmitirá de forma perezosa la lista y nunca construirá la lista en la memoria, todo sin usar ataques de IO inseguros. Para cambiar la cantidad de elementos que exiges, simplemente cambia el valor que pasas para take''

Para ver más ejemplos como este, lea el tutorial de pipes en Control.Pipe.Tutorial .

Para obtener más información acerca de por qué el IO lento causa problemas, lea las diapositivas originales de Oleg sobre el tema, que puede encontrar aquí . Él hace un gran trabajo explicando los problemas con el uso de IO perezoso. Cada vez que te sientas obligado a usar IO perezoso, lo que realmente quieres es una biblioteca iteratee.