lazy版本的mapM

时间:2012-09-26 19:42:34

标签: haskell io lazy-sequences

假设我在使用IO时获得了大量项目:

as <- getLargeList

现在,我正在尝试将fn :: a -> IO b应用到as

as <- getLargeList
bs <- mapM fn as

mapM的类型为mapM :: Monad m => (a -> m b) -> [a] -> m [b],这就是我在类型匹配方面所需要的。但它会在内存中构建所有链,直到返回结果。我正在寻找mapM的模拟,它会懒惰地工作,所以当尾巴还在建造时我可以使用bs的头部。

2 个答案:

答案 0 :(得分:18)

请勿使用unsafeInterleaveIO或任何惰性IO。这正是创建迭代器以解决的问题:避免惰性IO,这会产生不可预测的资源管理。诀窍是永远不会构建列表并不断使用迭代器对其进行流式处理,直到您完成使用它为止。我将使用我自己的库pipes中的示例来演示这一点。

首先,定义:

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

现在让我们对用户有意义并要求他们为我们制作真正大的名单:

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

现在,让我们运行它:

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

它只提示用户输入一个整数,因为我们只需要一个整数!

如果您想将prompt100替换为getLargeList的输出,请写下:

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

...然后运行:

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

这将懒惰地流式传输列表,并且永远不会在内存中构建列表,所有这些都不会使用不安全的IO黑客。要更改您需要的元素数量,只需将传递的值更改为take'

有关此类更多示例,请阅读Control.Pipe.Tutorial处的pipes tutorial

要详细了解懒惰IO导致问题的原因,请阅读Oleg关于主题的原始幻灯片,您可以找到here。他在解释使用惰性IO的问题方面做得很好。任何时候你觉得有必要使用懒惰的IO,你真正想要的是一个iteratee库。

答案 1 :(得分:6)

IO monad确实有一种延迟效果的机制。它被称为unsafeInterleaveIO。您可以使用它来获得所需的效果:

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)

这是懒惰IO的实现方式。实际上执行效果的顺序难以预测并且将由结果列表的元素的评估顺序决定,这是不安全的。出于这个原因,重要的是f中的任何IO效果都是良性的,因为它们应该对顺序不敏感。通常足够良性的效果的一个很好的例子是从只读文件中读取。