la版本的mapM [英] lazy version of mapM

查看:109
本文介绍了la版本的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆