2012-09-26 24 views
11

Supongamos, estoy recibiendo larga lista de elementos de trabajo con IO:versión lenta de mapm

as <- getLargeList 

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

as <- getLargeList 
bs <- mapM fn as 

mapM tiene escriba mapM :: Monad m => (a -> m b) -> [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 análogo de mapM, que funcionará perezosamente, por lo que puedo usar la cabeza de bs mientras cola todavía está construyendo.

+1

Quizás esto ayude. http://stackoverflow.com/questions/3270255/is-haskells-mapm-not-lazy –

+0

Anton, he leído este tema, pero no encontré la respuesta: ¿hay una alternativa de mapM para cálculos perezosos? –

+0

@DmitryBespalov No con el mismo tipo de firma. 'Monad' no tiene una abstracción para diferir los efectos hasta más tarde, y eso es lo que tendrías que hacer para que' mapM' sea más flojo. – Carl

Respuesta

18

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 está en . Nunca compile la lista y transmítala constantemente utilizando repeticiones hasta que haya terminado de usarla. Usaré ejemplos de mi propia biblioteca, pipes, para demostrar esto.

En primer lugar, definir:

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 vamos a ser malo con nuestro usuario y la demanda que producen la lista muy grande para nosotros:

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 entero!

Si desea reemplazar prompt100 con salida de getLargeList, que acaba de escribir:

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

... y luego ejecutar:

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

Esto se perezosamente transmitir la lista y nunca construir el lista en la memoria, todo sin usar hacks inseguros IO. Para cambiar la cantidad de elementos que exige, simplemente cambie el valor que pase a take'

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

Para obtener más información acerca de por qué la IO lenta causa problemas, lea las diapositivas originales de Oleg sobre el tema, que puede encontrar en here.É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.

+5

+100 para la biblioteca de tuberías –

+2

Gabriel, ¡gracias por la respuesta! Y es una biblioteca realmente útil, ¡gracias! –

+0

@DmitryBespalov ¡De nada! Siempre estoy feliz de ayudar. –

6

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 IO perezoso. 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 cualquier efecto IO en f sea benigno, en el sentido de que debe ser insensible a los pedidos. Un buen ejemplo de un efecto normalmente benigno es leer de un archivo de solo lectura.

+1

Desde mi punto de vista, usar System.IO.Unsafe en Haskell es algo así como un truco. Prefiero evitar usarlo debido a su comportamiento "inseguro". Sin embargo, aprecio tu respuesta. –