2013-03-21 58 views
15

考虑下面的例子:在大名单正在使用mapM /序列被认为是好的做法?

safeMapM f xs = safeMapM' xs [] 
    where safeMapM' []  acc = return $ reverse acc 
      safeMapM' (x:xs) acc = do y <- f x 
            safeMapM' xs (y:acc) 

mapM return largelist  -- Causes stack space overflow on large lists 
safeMapM return largelist -- Seems to work fine 

使用mapM导致堆栈溢出的空间,同时safeMapM似乎很好地工作(使用GHC 7.6.1与-O2)。但是,我无法在Haskell标准库中找到与safeMapM类似的功能。

使用mapM(或sequence)仍然被认为是良好做法吗?
如果是这样,尽管存在堆栈空间溢出的危险,为什么它被认为是良好的做法?
如果不是您建议使用哪种替代方案?

+0

也许'mapM'更快,如果它不溢出,因为你不必'倒退'?你测量过了吗? – 2013-03-21 14:08:14

+0

你可以发布你用来测试的'Main'模块吗? – jberryman 2013-03-21 14:12:46

+4

此外,还有一些单子(如'Control.Monad.State.Lazy'),其中'take 100 <$> mapM id [1 ..]'终止。 'take 100 <$> safeMapM id [1 ..]'不可能终止,不管单子是什么 – 2013-03-21 15:06:05

回答

9

作为Niklas B.,mapM的语义是一个有效的正确折叠的语义,它在比翻转版本更多的情况下成功终止。一般而言,mapM更有意义,因为我们很少想要在庞大的数据列表上做出产生结果的地图。更常见的是,我们想要评估效应的这种列表,并且在这种情况下,mapM_sequence_会丢弃结果,通常是推荐的。

编辑:换句话说,尽管在这个问题提出的问题,mapMsequence常用且通常被认为是很好的做法。

+1

我希望我的代码能够在所有情况下运行,而不仅仅是“更多情况下”。很多效果都带有返回值,例如用户输入。 Niklas B.示例我可以在开发时捕获,部署后可能会发生堆栈空间溢出,并且可能会发生大型列表溢出。更难以找到imho – jonnydee 2013-03-21 17:57:27

+2

有不同的语义和不同的权衡。所以你需要选择一个适合你的情况。这就是答案!唯一的答案!您提供的其他代码也会在足够大的列表上失败 - 它只会占用较大的列表。你也可以在程序中添加堆栈空间。只是,你知道,了解权衡,然后做出选择。 – sclv 2013-03-21 18:25:17

6

如果是这样,尽管存在堆栈空间溢出的危险,为什么它被认为是良好的做法? 如果不是您建议使用哪种替代方案?

如果要处理生成的列表元素,请使用pipesconduit。两者都不会建立中间名单。

我会显示pipes的方式,因为那是我的图书馆。我会首先与来自用户输入IO单子产生数的无限名单开始:

import Control.Proxy 

infiniteInts :: (Proxy p) =>() -> Producer p Int IO r 
infiniteInts() = runIdentityP $ forever $ do 
    n <- lift readLn 
    respond n 

现在,我想打印出来,因为它们产生的。这需要定义一个下游处理程序:

printer :: (Proxy p) =>() -> Consumer p Int IO r 
printer() = runIdentityP $ forever $ do 
    n <- request() 
    lift $ print n 

现在我可以在ProducerConsumer使用(>->)连接,然后运行使用runProxy结果:

>>> runProxy $ infiniteInts >-> printer 
4<Enter> 
4 
7<Enter> 
7 
... 

那么这将读取用户的Int S和回声他们回到控制台,因为他们没有在内存中保存多个元素。

所以通常情况下,如果你想要一个有效的计算来生成元素流并立即使用它们,你不需要mapM。使用合适的流媒体库。

如果您想了解更多关于pipes的信息,我建议您拨打reading the tutorial

+0

这个答案有点过时了。你可能想要更新它。 – dfeuer 2017-08-02 17:25:18

-1

如果你想留在懒惰的营地,lazyio包允许你懒惰地处理输入列表。取而代之的

mapM f 

你写

import qualified System.IO.Lazy as LazyIO 

LazyIO.run . mapM (LazyIO.interleave . f) 

没有栈溢出了。

相关问题