不要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'
有关更多此类示例,请阅读pipes
教程。Control.Pipe.Tutorial
要了解有关惰性 IO 为何会导致问题的更多信息,请阅读 Oleg 关于该主题的原始幻灯片,您可以在此处找到。他很好地解释了使用惰性 IO 的问题。任何时候你不得不使用惰性 IO,你真正想要的是一个迭代库。