将Scala continuations与while循环一起使用

将Scala continuations与while循环一起使用,scala,continuations,delimited-continuations,Scala,Continuations,Delimited Continuations,我意识到这与通常意义上的SO问题背道而驰,但是下面的代码可以工作,尽管我认为它不应该工作。下面是一个小型Scala程序,它使用带while循环的continuations。根据我对延续传递样式的理解,这段代码应该通过为while循环的每次迭代向堆栈添加一个帧来产生堆栈溢出错误。然而,它工作得很好 import util.continuations.{shift, reset} class InfiniteCounter extends Iterator[Int] { var coun

我意识到这与通常意义上的SO问题背道而驰,但是下面的代码可以工作,尽管我认为它不应该工作。下面是一个小型Scala程序,它使用带while循环的continuations。根据我对延续传递样式的理解,这段代码应该通过为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())
    }

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


(这个实验是在github上进行的,同时还有运行它所需的sbt配置:。请参阅提交7CEC9BEFC58820B925BB22BC25F2A48CBEC4A6)

这里没有出现堆栈溢出的原因,因为您使用的
shift
回调()
的方式就像一个函数

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

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

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


我觉得一个图表可以让这一点更清楚,但是使用调试器单步执行代码可能也同样有用。我认为这里的关键点是,既然你基本上已经建造了一个蹦床,你就永远不会把事情搞砸。

我认为这是一个很好的解释,可以解释一些非常令人困惑的事情。我可能会尝试用视觉的方式来描绘这个,如果我创建了一些说明性的东西,我会在这里发布一个链接。澄清一下,这意味着这种构造不仅不会破坏堆栈,而且总内存需求也是适度的,并不取决于“迭代次数”@jcrudy-是的,内存需求独立于
for
表达式中的迭代次数。作为一个简单的测试,我运行了如下代码:
JAVA_OPTS=“-Xmx2M-verbose:gc”scala-Experiment3
(这在我的笔记本电脑上运行)。这证明了100M迭代只需要2MB的堆空间。添加详细垃圾收集选项还可以让您看到,在整个执行过程中,实际内存使用量几乎保持不变
1
Hello
2
100000003