la版本的mapM [英] lazy version of mapM
问题描述
假设,我在处理IO时收到大量项目列表:
as< - getLargeList
现在,我正在尝试应用 fn :: a - >作为
:
as < - getLargeList
bs< - mapM fn as
mapM
的类型为 mapM :: Monad m => (a - > m b) - > [a] - > m [b]
,这就是我需要的类型匹配。但它会建立内存中的所有链,直到返回结果。我正在寻找类似于 mapM
的工具,该工具将会延迟工作,以便我可以在尾部使用 bs
仍然在建设中。
不要使用 unsafeInterleaveIO
对于这个问题。这正是迭代创建时要解决的问题:避免惰性IO,这会导致不可预知的资源管理。诀窍是永远不要构建列表,并不断使用迭代对其进行流式处理,直到完成使用。我将使用自己的库中的示例 pipes
来证明这一点。首先,define:
导入Control.Monad
导入Control.Monad.Trans
导入Control.Pipe
- 仅需'n'元素
取'::(Monad m)=> Int - > Pipe aam()
take'n = replicateM_ n $ do
a <等待
产生一个
- 打印所有传入元素
printer: :(Show a)=>消费者a IO r
打印机=永远$ do
a < - 等待
抬起$打印a
现在让我们对我们的用户和需求表示他们为我们制作的真正大的名单:
prompt100 :: Producer Int IO()
prompt100 = replicateM_ 1000 $ do
lift $ putStrLn输入一个整数:
n< - lift readLn
yield n
现在,让我们运行它:
>>> runPipe $ printer< +<采取'1 +< prompt100
输入一个整数:
3< Enter>
3
它只会提示用户一个整数,因为我们只需要一个整数!
如果您想用 getLargeList
的输出替换 prompt100
>,您只需写:
yourProducer :: Producer b IO()
yourProducer = do
xs < - lift getLargeList
mapM_ yield xs
...然后执行: p>
>>> runPipe $ printer< +<采取'1 +< yourProducer
这将懒散地列出列表,并且永远不会在内存中构建列表,而且不会使用不安全的 IO
hacks。要更改您需要的元素数量,只需将您传递的值更改为 take'
,阅读 管道
教程在 Control.Pipe.Tutorial
。
要详细了解为什么惰性IO导致问题,请阅读Oleg关于主题的原始幻灯片,您可以在其中找到此处。他擅长解释使用惰性IO的问题。任何时候你感觉不得不使用惰性IO,你真正想要的是一个迭代库。
Suppose, I'm getting large list of items while working with IO:
as <- getLargeList
Now, I'm trying to apply fn :: a -> IO b
onto as
:
as <- getLargeList
bs <- mapM fn as
mapM
has type mapM :: Monad m => (a -> m b) -> [a] -> m [b]
, and that's what I need in terms of type matching. But it builds all the chain in memory until return the result. I'm looking for analog of mapM
, which will work lazily, so that I may use head of bs
while tail is still building.
Do not use unsafeInterleaveIO
or any lazy IO for that matter. This is precisely the problem that iteratees were created to address: avoiding lazy IO, which gives unpredictable resource management. The trick is to never build the list and constantly stream it using iteratees until you are done using it. I will use examples from my own library, pipes
, to demonstrate this.
First, define:
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
Now let's be mean to our user and demand they produce the really large list for us:
prompt100 :: Producer Int IO ()
prompt100 = replicateM_ 1000 $ do
lift $ putStrLn "Enter an integer: "
n <- lift readLn
yield n
Now, let's run it:
>>> runPipe $ printer <+< take' 1 <+< prompt100
Enter an integer:
3<Enter>
3
It only prompts the user for one integer, since we only demand one integer!
If you want to replace prompt100
with output from getLargeList
, you just write:
yourProducer :: Producer b IO ()
yourProducer = do
xs <- lift getLargeList
mapM_ yield xs
... and then run:
>>> runPipe $ printer <+< take' 1 <+< yourProducer
This will lazily stream the list and never build the list in memory, all without using unsafe IO
hacks. To change how many elements you demand, just change the value you pass to take'
For more examples like this, read the pipes
tutorial at Control.Pipe.Tutorial
.
To learn more about why lazy IO causes problems, read Oleg's original slides on the subject, which you can find here. He does a great job of explaining the problems with using lazy IO. Any time you feel compelled to use lazy IO, what you really want is an iteratee library.
这篇关于la版本的mapM的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!