如何在monad中懒洋洋地构建Haskell列表?
考虑以下两个几乎相同的函数:如何在monad中懒洋洋地构建Haskell列表?,haskell,monads,Haskell,Monads,考虑以下两个几乎相同的函数: buildList 0 = [] buildList n = n : buildList (n - 1) buildListM 0 = return [] buildListM n = buildListM (n - 1) >>= return . (n :) Haskell的惰性特性允许buildList生成列表,而不会在内存中产生太多开销,因为它生成列表的头部,然后生成尾部 但是一元版本的buildListM似乎使用了更多的内存,因为n变得越来越
buildList 0 = []
buildList n = n : buildList (n - 1)
buildListM 0 = return []
buildListM n = buildListM (n - 1) >>= return . (n :)
Haskell的惰性特性允许buildList
生成列表,而不会在内存中产生太多开销,因为它生成列表的头部,然后生成尾部
但是一元版本的buildListM
似乎使用了更多的内存,因为n
变得越来越大,因为它必须先构建尾部,然后再构建头部
这是在monad内部构建列表的一种好方法,还是有更有效的方法?许多monad
(例如,IO
、strictST s
、strictState s
)是“严格的”,因为=
在其左操作数中是严格的。另一些,最显著的是标识
,但也有(>)a
、lazy状态s
、lazy编写器q
、lazyST s
,都是“懒惰”的,因为>=
在其左操作数中并不严格。考虑在<代码>标识< /代码>单元格中使用<代码> TelistM<代码>:
buildListM 0 = return []
buildListM n = buildListM (n - 1) >>= return . (n :)
这里,return=Identity
和Identity m>=f=fm
,所以
buildListM 0 = Identity []
buildListM n = Identity . (n :) $ runIdentity (buildListM (n - 1))
= Identity (n : runIdentity (buildListM (n - 1)))
由于Identity
和runIdentity
在运行时是完全无操作的,buildListM
在Identity
monad中运行时实际上与buildList
相同。使事情变得严格的是特定的单子,而不是单子的本性
有时,您可以通过以下两种方式之一在“严格”单子中执行惰性操作:
unsafeInterleaveIO
或unsafeInterleaveST
作弊MonadFix
抽象,它可以让您在计算结果被执行之前获得它,但是如果您在计算结果可用之前访问它,它将失败如果列表生成本身就是一个纯计算,请使用
let
。如果它是一元运算的结果,你必须更具体,“它必须先构建尾部,然后在头部前面”——这一说法取决于单子。例如,State
monad使buildListM
非常有效——试着抛开基本情况,在evalState(buildListM 0)(
之后看着数字尖叫而过。正如其他人所说,monad计算的语义取决于monad。但是,如果monad的bind运算符太严格,您仍然会遇到前面描述的问题。众所周知,对于某些过于严格的单子,默认的序列和friends具有O(n^2)复杂性。有关在现实世界中发生的此问题以及如何解决此问题的相当深入的分析,请参阅。扰流板:解决方案是推出自己的sequence/mapM/replicateM等高效实现。谢谢。这些都是很好的解释来反驳我的假设。