使用带循环的Scala延续

时间:2013-12-21 01:38:30

标签: scala continuations delimited-continuations

我意识到这与通常的SO问题相反,但是即使我认为它不起作用,下面的代码也能正常工作。下面是一个小型Scala程序,它使用while循环的continuation。根据我对连续传递样式的理解,这段代码应该通过在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。请参阅commit 7cec9befcf58820b925bb222bc25f2a48cbec4a6)

1 个答案:

答案 0 :(得分:7)

由于您使用shiftcallback()的方式就像 trampoline ,因此您没有在此处获得堆栈溢出的原因。

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

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

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

我觉得图表会让这个更清晰 - 但是使用调试器逐步执行代码可能会同样有用。我认为这里的关键点是,因为你基本上建造了一个蹦床,所以你永远不会砸到堆叠。