假设,我得到的项目大名单与IO工作时:
as <- getLargeList
现在,我想申请fn :: a -> IO b
到as
:
as <- getLargeList
bs <- mapM fn as
mapM
具有类型mapM :: Monad m => (a -> mb) -> [a] -> m [b]
而这正是我需要类型匹配的条款。 但是它建立在内存中的所有链条,直到返回结果。 我在寻找的模拟mapM
,这将懒洋洋地工作,这样我可以用头bs
而尾部还在建。
不要使用unsafeInterleaveIO
或懒惰IO为这一问题。 这正是iteratees创建来解决这个问题:避免懒IO,这给不可预测的资源管理。 诀窍是永远不会生成列表不断采用iteratees它,直到你用它做流。 我将用例子从我自己的图书馆, 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'
欲了解更多像这样的例子,阅读pipes
教程在Control.Pipe.Tutorial
。
要了解更多关于为什么懒IO引起的问题,阅读奥列格关于这个问题,你可以找到原来的幻灯片在这里 。 他做了解释,使用懒IO的问题的一个伟大的工作。 你觉得有必要使用延迟IO任何时候,你真正想要的是一个iteratee库。
该IO单子是否有一个机构推迟的影响。 这就是所谓的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是如何实现的懒惰。 是不安全的意义上,在其中的影响实际上将被执行的顺序是很难预测,并且将通过在结果列表中的元素的计算顺序来确定。 出于这个原因,重要的是,在任何IO效果f
是良性的,因为他们应该是为了不敏感的感觉。 一个通常足够良性的效果一个很好的例子是从只读文件读取。