有人对它的工作原理有很好的指导吗?带有视觉辅助的东西会很好,我遇到的每一个指南似乎都在说同样的事情,我需要重新审视它。
5 回答
这是我们 CS 实验室白板上留下的图表。所以你要取一些苹果,然后在开始之前抓住一个延续。您在森林中漫步,收集苹果,最后您将延续应用到苹果上。突然,你发现自己在进入森林之前所处的位置,除了你所有的苹果。
(display
(call/cc (lambda (k)
(begin
(call-with-forest
(lambda (f)
(k (collect-apples f))))
(get-eaten-by-a-bear)))))
=> some apples (and you're not eaten by a bear)
我认为可能涉及成年礼和埋藏的黄金。
Have a look at the continuation part of PLAI -- it's very "practical oriented", and it uses a "black-hole" visualization for continuations that can help you understand it.
我发现它有助于可视化调用堆栈。在评估表达式时,请跟踪每一步的调用堆栈。(参见例如http://4.flowsnake.org/archives/602)起初这可能是不直观的,因为在大多数语言中调用堆栈是隐式的;你不能直接操纵它。
现在将延续视为保存调用堆栈的函数。当调用该函数时(使用值 X),它会恢复保存的调用堆栈,然后将 X 传递给它。
学习 call/cc 没有捷径可走。阅读方案编程语言中的章节或在 Fixnum Days 中自学方案。
从不喜欢 call/cc 的可视化表示,因为我无法将其反映到代码中(是的,想象力很差);)
无论如何,如果您已经熟悉其他语言的异常,我认为不从 call/cc 而是从 call/ec(转义继续)开始会更容易。
这是一些应该评估为价值的代码:
(lambda (x) (/ 1 x))
如果 x 等于“0”怎么办?在其他语言中我们可以抛出异常,那么方案呢?我们也可以扔!
(lambda (x) (call/ec (cont)
(if (= x 0) (cont "Oh noes!") (/ 1 x))))
call/ec(以及 call/cc)在这里的工作方式类似于“try”。在命令式语言中,您可以轻松地跳出函数,只需返回值或抛出异常。在功能上你不能跳出来,你应该评估一些东西。并且 call/* 来救援。它的作用是将“call/ec”下的表达式表示为带有一个参数的函数(在我的例子中称为“cont”)。当这个函数被调用时,它会将整个调用 /* 替换为它的参数。
因此,当(cont "Oh noes!")
替换 (call/ec (cont) (if (= x 0) (cont "Oh noes!") (/ 1 x)))
为"Oh noes!"
字符串时。
call/cc 和 call/ec 几乎相等,除了 ec 更易于实现。它只允许跳上去,而cc可以从外面跳下来。