使用mapM / sequence被认为是好的做法吗?

时间:2013-03-21 11:25:57

标签: haskell

请考虑以下示例:

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似乎工作正常(使用带有-O2的GHC 7.6.1)。但是我无法在Haskell标准库中找到类似于safeMapM的函数。

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

3 个答案:

答案 0 :(得分:9)

作为Niklas B.,mapM的语义是有效的右折叠的语义,并且它在更多情况下比翻转版本成功终止。一般来说,mapM更有意义,因为我们很少想要在庞大的数据列表上做一个结果映射。更常见的是,我们要评估效果的这样一个列表,在这种情况下,mapM_sequence_会抛弃结果,通常是推荐的。

编辑:换句话说,尽管问题中出现了问题,但mapMsequence是常用的,通常被视为良好做法。

答案 1 :(得分:7)

  

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

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

我将展示pipes方式,因为那是我的库。我首先从用户输入IO monad中生成的无限数字列表开始:

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

现在,我可以使用Producer关联Consumer(>->),并使用runProxy运行结果:

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

然后将从用户读取Int并在生成它们时将它们回送到控制台,而不会在内存中保存多个元素。

通常,如果您想要一个有效的计算来生成元素流并立即使用它们,那么您不需要mapM。使用正确的流媒体库。

如果您想了解有关pipes的更多信息,请参阅reading the tutorial

答案 2 :(得分:-1)

如果你想留在懒惰的阵营,lazyio包允许你懒洋洋地处理输入列表。而不是

mapM f
你写了

import qualified System.IO.Lazy as LazyIO

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

不再有堆栈溢出。