不使用unsafeInterleaveIO
或任何与此相关的惰性 IO。这正是创建 iteratee 来解决的问题:避免惰性 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教程 http://hackage.haskell.org/packages/archive/pipes/2.3.0/doc/html/Control-Pipe-Tutorial.html at Control.Pipe.Tutorial
.
要了解有关惰性 IO 为何会导致问题的更多信息,请阅读 Oleg 关于该主题的原始幻灯片,您可以找到该幻灯片here http://okmij.org/ftp/Haskell/Iteratee/IterateeIO-talk-notes.pdf。他很好地解释了使用惰性 IO 的问题。任何时候你觉得有必要使用惰性 IO 时,你真正想要的是一个 iteratee 库。