【问题标题】:Why does a recursive lazy list blow the stack in Scala?为什么递归惰性列表会破坏 Scala 中的堆栈?
【发布时间】:2017-04-21 05:56:50
【问题描述】:

我在 Meta PPCG 上的 proud haskeller this answer 中看到了 Haskell 的这个 sn-p:

x=2:x

我想,“等等,我可以在 Scala 中做到这一点!”所以我尝试了:

lazy val x: List[Int] = 2 :: x

它编译了,我的控制台打印了一个漂亮的x: List[Int] = <lazy>。但是这些行中的每一行都会导致StackOverflowException

x take 1
x.head
x(1)
x

基于最后一个,看起来任何使用x 的尝试都会破坏试图计算x 的堆栈(要么发生堆栈溢出,要么试图在控制台中打印它)。在这个例子中,Scala 的惰性与 Haskell 的惰性有何不同?这是 Scala 的 lazy val 的一个特性还是 List 类只需要一个完整的尾部?

【问题讨论】:

    标签: scala haskell linked-list lazy-evaluation


    【解决方案1】:

    你想要的是def x: Stream[Int] = 2 #:: x。这会产生一个immutable.Stream[Int]

    惰性变量仅在需要时才被评估,但它会被完全评估。另一方面,Stream 是惰性值的集合。每个元素只在需要时才被评估,但整个集合可能永远不会被评估,这就是为什么它可以是无限的。

    【讨论】:

    • 不完全是,你需要返回类型来编译它,但仍然很好。懒惰的 val 是 lazy val x: Stream[Int] = 2 #:: x.
    • @BrianMcCutchon,没错。忙着写第二段,错过了。然而,我认为让Stream val 变得懒惰毫无意义。如果是val,则结果(评估的元素)会被记忆。如果是var,则不是。将其设为lazy val 并不会为您带来太多收益。
    • 糟糕。另一个拼写错误试图澄清val 流之间的区别,它被记忆了,def(不是var)流,它没有被记忆。
    【解决方案2】:

    好吧,看来我是在提出问题时想出来的。 List 似乎比lazy val 更成问题。为了尝试这个,我做了一个简单的LazyList 实现:

    class LazyList(h: Int, t: => LazyList) {
      val head = h
      lazy val tail = t
    }
    

    那我可以做:

    lazy val x: LazyList = new LazyList(1, x)
    x.head // 1
    x.tail.tail.tail.head // 1
    

    所以,Scala 的懒惰毕竟是真的懒惰,如果你让一切都变得懒惰,至少。

    【讨论】:

    • 请注意,如果您在 Haskell 中使用严格的列表,也会发生完全相同的事情。
    • @JörgWMittag 有趣。我对 Haskell 知之甚少,但我很惊讶它有一个严格的列表类型。
    猜你喜欢
    • 2012-07-14
    • 1970-01-01
    • 2018-05-23
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 2021-03-14
    • 1970-01-01
    相关资源
    最近更新 更多