如何在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
、strict
ST s
、strict
State s
)是“严格的”,因为
=
在其左操作数中是严格的。另一些,最显著的是
标识
,但也有
(>)a
、lazy
状态s
、lazy
编写器q
、lazy
ST 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等高效实现。谢谢。这些都是很好的解释来反驳我的假设。