请考虑以下示例:
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
是否仍然是一种良好做法)?
如果是这样,尽管存在堆栈空间溢出的危险,为什么它被认为是好的做法?
如果不是您建议使用哪种替代方案?
答案 0 :(得分:9)
作为Niklas B.,mapM
的语义是有效的右折叠的语义,并且它在更多情况下比翻转版本成功终止。一般来说,mapM
更有意义,因为我们很少想要在庞大的数据列表上做一个结果映射。更常见的是,我们要评估效果的这样一个列表,在这种情况下,mapM_
和sequence_
会抛弃结果,通常是推荐的。
编辑:换句话说,尽管问题中出现了问题,但是,mapM
和sequence
是常用的,通常被视为良好做法。
答案 1 :(得分:7)
如果是这样,尽管存在堆栈空间溢出的危险,为什么它被认为是好的做法? 如果不是您建议使用哪种替代方案?
如果要在生成列表元素时对其进行处理,请使用pipes
或conduit
。两者都不会建立中间名单。
我将展示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)
不再有堆栈溢出。