Haskell 使用mapM/序列是否被视为良好实践?
考虑以下示例:Haskell 使用mapM/序列是否被视为良好实践?,haskell,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 o
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
safeMapM f xs=safeMapM'xs[]
其中safeMapM'[]acc=返回$reverse acc
safeMapM'(x:xs)acc=do y正如Niklas B所说,mapM
的语义是有效的右折叠语义,并且它在比翻转版本更多的情况下成功终止。一般来说,mapM
更有意义,因为我们很少希望在大量数据列表上生成结果映射。更常见的情况是,我们需要评估这样一个列表的效果,在这种情况下,通常推荐使用丢弃结果的mapM\uu
和sequence\u
编辑:换句话说,尽管问题中提出了问题,yes、mapM
和sequence
通常被认为是良好的实践
如果是这样的话,尽管存在堆栈空间溢出的危险,为什么认为这是一种良好的做法?
如果没有,您建议使用哪种替代方案
如果要在生成列表元素时对其进行处理,请使用管道
或导管
。两者都不会建立一个中间列表
我将显示管道
方式,因为这是我的库。首先,我将从用户输入在IO
monad中生成的无限数字列表开始:
import Control.Proxy
infiniteInts :: (Proxy p) => () -> Producer p Int IO r
infiniteInts () = runIdentityP $ forever $ do
n <- lift readLn
respond n
然后,它将从用户处读取Int
s,并在生成时将其回显到控制台,而不会在内存中保存超过一个元素
因此,通常,如果您想要生成元素流并立即使用它们的有效计算,您不需要mapM
。使用适当的流媒体库
如果您想了解有关管道的更多信息,那么我建议。如果您想留在惰性阵营,该软件包允许您惰性地处理输入列表。而不是
mapM f
你写
import qualified System.IO.Lazy as LazyIO
LazyIO.run . mapM (LazyIO.interleave . f)
不再有堆栈溢出。如果不溢出,可能mapM
会更快,因为您不必反转
?你测量过了吗?你能发布你用来测试的Main
模块吗?还有一些Monad(比如Control.Monad.State.Lazy
),比如take 100 mapM id[1..]
终止take 100 safeMapM id[1..]
不可能终止,无论monad@jberrymanmain=mapmreturn[1..10000000]>>return()
@NiklasB。我并不建议将safeMapM作为更好的解决方案。事实上,你说明了一个理由。但是,虽然mapM似乎很常见,但它有一个潜在的堆栈空间溢出的缺点。我的问题是为什么或者何时(不)应该使用mapM?我希望我的代码在所有情况下都能运行,而不仅仅是在“更多情况下”。很多效果都与返回值一起出现,例如用户输入。Niklas B.我在开发时可以捕捉到的示例是,部署后可能会出现带有大列表的堆栈空间溢出。更难找到imhot的是不同的语义和不同的权衡。所以你需要选择一个适合你的情况。这就是答案!唯一的答案!您提供的其他代码在足够大的列表上也会失败——它只会占用较大的列表。您也可以只向程序添加堆栈空间。只是,你知道,理解折衷,然后做出选择。这个答案有点过时了。您可能需要更新它。
mapM f
import qualified System.IO.Lazy as LazyIO
LazyIO.run . mapM (LazyIO.interleave . f)