【问题标题】:Using Scala continuations with while loops将 Scala 延续与 while 循环一起使用
【发布时间】:2014-01-09 22:59:08
【问题描述】:

我意识到这与 SO 问题的通常意义背道而驰,但即使我认为它不应该工作,以下代码仍然有效。下面是一个小的 Scala 程序,它使用带有 while 循环的延续。根据我对延续传递风格的理解,这段代码应该会通过为 while 循环的每次迭代向堆栈添加一个帧来产生堆栈溢出错误。但是,它工作得很好。

import util.continuations.{shift, reset}


class InfiniteCounter extends Iterator[Int] {
    var count = 0
    var callback: Unit=>Unit = null
    reset {
        while (true) {
            shift {f: (Unit=>Unit) =>
                callback = f
            }
            count += 1
        }

    }

    def hasNext: Boolean = true

    def next(): Int = {
        callback()
        count
    }
}

object Experiment3 {

    def main(args: Array[String]) {
        val counter = new InfiniteCounter()
        println(counter.next())
        println("Hello")
        println(counter.next())
        for (i <- 0 until 100000000) {
            counter.next()
        }
        println(counter.next())
    }

}

输出是:

1
Hello
2
100000003

我的问题是:为什么没有堆栈溢出? Scala 编译器是在做尾调用优化(我认为它不能用延续做)还是有其他事情发生?

(此实验在 github 上以及运行它所需的 sbt 配置:https://github.com/jcrudy/scala-continuation-experiments。参见提交 7cec9befcf58820b925bb222bc25f2a48cbec4a6)

【问题讨论】:

    标签: scala continuations delimited-continuations


    【解决方案1】:

    这里没有出现堆栈溢出的原因是您使用shiftcallback() 的方式就像一个trampoline

    每次执行线程到达shift 构造时,它会将callback 设置为当前的延续(闭包),然后立即将Unit 返回到调用上下文。当您调用next() 并调用callback() 时,您会执行延续闭包,它只执行count += 1,然后跳回到循环的开头并再次执行shift

    CPS 转换的主要好处之一是它在延续中捕获控制流,而不是使用堆栈。当您在每个“迭代”上设置 callback = f 时,您将覆盖您对函数先前继续/状态的唯一引用,这允许它被垃圾收集。

    这里的堆栈只达到几帧的深度(由于所有嵌套的闭包,它可能在 10 左右)。每次执行 shift 时,它都会在闭包(堆中)中捕获当前状态,然后堆栈展开回您的 for 表达式。

    我觉得图表会让这更清楚——但使用调试器单步执行代码可能同样有用。我认为这里的关键点是,由于您基本上已经构建了一个蹦床,所以您永远不会破坏堆栈。

    【讨论】:

    • 我认为这是对一些令人困惑的事情的一个很好的解释。我可能会尝试在视觉上勾勒出来,如果我创建一些说明性的东西,我会在这里发布一个链接。澄清一下,这意味着这种构造不仅不会破坏堆栈,而且总内存需求也是适中的,并且不依赖于“迭代”的次数?
    • @jcrudy - 是的,内存需求与for 表达式中的迭代次数无关。作为一个简单的测试,我像这样运行您的代码:JAVA_OPTS="-Xmx2M -verbose:gc" scala Experiment3(这适用于我的笔记本电脑)。这证明了 100M 的迭代只需要 2MB 的堆空间。添加详细垃圾收集选项还可以让您看到实际内存使用量在整个执行过程中几乎保持不变。
    • Rich Dougherty 写了一篇关于 Scala 蹦床的精彩博文:blog.richdougherty.com/2009/04/…
    猜你喜欢
    • 2014-12-20
    • 2014-04-11
    • 2013-07-06
    • 2017-10-09
    • 2020-08-25
    • 1970-01-01
    • 2011-08-06
    • 2015-01-21
    • 1970-01-01
    相关资源
    最近更新 更多