有人可以用简单的词语解释呼叫/ cc吗?

时间:2014-04-06 00:50:32

标签: racket

我正在研究语言球拍并试图掌握/ cc实际上是为了什么。有人可以用简单的语言解释一下,举一两个例子吗?感谢。

2 个答案:

答案 0 :(得分:9)

如果您有一个表达式(+ (* 2 3) (/ 10 2)),则Scheme系统不会同时评估所有内容,而是部分评估。方案没有在Scheme中指定,但让我们想象它是从左到右(我认为Racket总是从左到右):

您执行(* 2 3),继续计算(/ 10 2),然后(+ result1 result2)。 Scheme系统可以这样做的方法是在执行之前将代码转换为Continuation passing style。上面的表达式变成了这样的东西:

 (lambda (k)
  (*& 2 
      3
      (lambda (result1)
        (/& 10
            2
            (lambda (result2)
              (+& result1 result2 k))))))

现在,最后&的过程与Scheme中的过程相同,除了它是最后一个参数的延续。其中一个示例:(define (+& a b k) (k (+ a b)))。所有其他的都是这样完成的,被认为是原始的。

如果您申请并传递displayvalues,则会显示或评估为11。 但是,如果您使用call/cc,则可以覆盖它。想象一下这个变种:

(call/cc (lambda (k) (+ (* 2 3) (/ 10 (if (zero? a) (k +inf.0) a))

在Scheme中,如果除以零,则会出现错误,如果发生这种情况,您需要取消其余的计算,并说结果是无限的。上面的代码就是这样,如果a为零,它将不会对前两个计算的结果求和。它实际上充当GOTO。该代码的CPS版本如下所示:

(lambda (k)
   (*& 2 
       3
       (lambda (result1)
         (zero?& a 
                 (lambda (azero?)
                   (if azero?
                       (k +inf.0) ; continuation used here
                       (/& 10
                           a
                           (lambda (result2)
                             (+& result1 result2 k))))))))) ; and here

那么call/cc做了什么?那么它可以让你按常规方式编码,而不是CPS就像计算机如何进行实际计算,但是通过掌握延续来获得最佳的两个世界,这样你就可以像在CPS中编写它一样。

现在,想象一下这段代码:

(let* ((c 10)
       (r (call/cc (lambda (exit) exit))))
  (display "Hello\n")
  (cond ((zero? c) 'done)
        (else (set! c (- c 1))
              (r r))))

在这里,您看到我将续集作为r返回。继续是其余的计算,从设置r开始,然后执行display ...这实际上显示“hello \ n”11次,因为当你在底部调用延续时它会做同样的事情一遍又一遍。

就像eval一样,我确实试着将它保持在绝对最小值,当我这样做时,我通常会这样做以从正在进行的计算中获得中止。例如。

(define (lstadd1 lst)
  (call/cc (lambda (exit)
    (let loop ((lst lst))
       (cond ((pair? lst) (cons (add1 (car lst)) (loop (cdr lst))))
             ((null? lst) '())
             (else (exit #f))))))) ; not proper 

(lstadd1 '(1 2 3))   ; ==> (2 3 4)
(lstadd1 '(1 2 . 3)) ; ==> #f

有关更多示例I love Matt Mights page,其中有很多关于如何使用continuation的示例。

答案 1 :(得分:6)

并非call/cc的所有实现都完全相同,但希望这个答案适用于包括Racket在内的所有常见变体。这个故事实际上是基于Unlambda内置的c

呼叫的隐喻/ cc

你是一位超级英雄考古学家,在一个古老的玛雅遗址上挖掘。您将在优雅的拱门中挖掘出精美的,保存完好的石头门廊。但它只是一个门口,已经摆在一边;它附近没有任何墙壁,它似乎也不是墙壁的一部分。您的员工会从中获取能量读数,因此您将其完整地运送回实验室进行学习。

在你的实验室里,墙上挂着一个大钟,直接放在现在直立的拱门前面,你已放置在房间中间附近。在检查拱门时,你会走过它。

下午4:17

走过拱门之后,你会惊讶地发现,你手里拿着一个立方体盒子,大到足以放入一本书,里面有一个盖子和一个发光的按钮。你不记得拿起这样的盒子,或者之前看过它。你打电话给你的助手,询问盒子的来源。助手不知道。如果放下盒子,按钮会停止发光。你再次拿起它,按钮再次发光。只有握住它才会发光;如果助手拿起盒子就不会发光。在百灵鸟上,你将一个回形针放入盒子里,盖上盖子,然后按下按钮。

下午4:23

走过拱门之后,你会惊讶地发现,你手里拿着一个回形针。你不记得把它捡起来。你的助手在房间里,盯着你傻眼了。你不记得你的助手进入房间。你的时钟似乎也快几分钟。

"刚刚发生了什么?"问助手。

"我刚走过拱门,"你说,不是真的理解这个问题。

"不,你没有!盒子里发生了什么?"助手说。

"你在说什么?"你说,越来越恼火。

...

一旦整个情况发生,你就会了解这个拱门的作用,你决定做一些大胆的事情。你大声朗读当前时间,然后走过拱门。

下午5:30

从拱门出现,你拿着一个空盒子。观察时间是您预期的下午5:30,您可以在标有#1的方框中附上便条。你把盒子放在桌子上,大声朗读当前时间,再次走过拱门。

下午5:31

从拱门出现,你拿着一个空盒子。观察到时间是您预期的下午5:31,您可以在标有#2 - use to forget的框中附加便利贴。您将#2 框放在框#1中(它缩小到它的大小的一小部分;看起来这些框是为此设计的。)

作为一名超级英雄考古学家,你可以适当地装备自己并闯入你最不喜欢的压迫政权的外国大使馆,破坏它的金库并窃取一些密切保存的国家机密的纸质副本。香港动作片的东西,子弹和拳头飞舞。你将自己设置在金库中,在他们的警卫到达你之前购买宝贵的秒钟。从你的包里拿出#1盒子,你把纸张塞进里面(同时也不在里面的#2盒子里面),关闭盒子#1,就像金库门被打开一样,你微笑,拉别针,放下手榴弹,按下按钮。

晚上9:45

从拱门出现,你拿着一个空盒子,上面标有#2 - use to forget和含有你最不喜欢的压迫政权敏感国家机密的文件。您还注意到时间不是您预期的下午5:30,而是下午9:45,因此您不会继续执行预定的闯入计划。您坐下来将文档的内容提交到内存中,一旦您确定将它们完全记忆,就可以将文档刻录到垃圾箱中。现在你大声朗读当前的时间并走过拱门。

凌晨2:00

从拱门出现,你拿着一个空盒子。观察当前时间是您预期的凌晨2:00,您可以立即标记新框#3 - use to remember。你给自己写了一个简短的注释:Allow self to forget again. At exactly 7:15 PM call police and report suspicious persons at 14th and Maple.放置方框#3和方框#2中的注释,然后点击方框#2上的按钮。

凌晨2:01

从拱门出现,你拿着一个空盒子,上面标有#3 - use to remember,还有一张你自己手写的便条。时间比预期的下午5:31晚得多,因此您不会继续执行预定的闯入计划。按照您的指示,再次走过拱门,获取一个标有#4 - use to forget的新框。你按照你的笔记中的指示打电话给警察,不知道为什么,并且在新闻日之后听到你家乡的国际间谍圈被打破了。

任务完成! (你假设。)从现在开始,你可以选择了解信息本身,但不知道你用它做了什么;或者不知道这些信息,但要知道它是如何被使用的。

最后,这个奇妙的工具付出了代价。当你继续在各种活动中依赖拱门时,你必须知道你永远会破坏你的生活,除非你在许多替代自我之间发送信息,否则碎片永远不会统一。单按一下按钮盒就会导致许多珍贵记忆的无法恢复,这可能是一种策略,也可能是一种严重而悲惨的错误。

说明

穿过拱门代表call/cc操作。这样做会导致创建一个新的按钮盒,它代表一个延续功能。将内容放入框中表示将参数传递给continuation函数。按下框上的按钮表示调用延续功能。

按下框上的按钮(调用延续函数)会导致传说中的字符(带变量的调用堆栈)恢复到创建框(继续)时的确切状态,即{{1最初完成了。传递给continuation的参数成为堆栈恢复后原始call/cc的结果值;这就是为什么按下按钮时,传说中的角色不是一个盒子而是盒子的内容。

Boxes(continuations)可以封装(相互传递),允许使用call/cc作为基元来实现协同例程,状态机和其他高级构造。

也可以使用Continuations轻松地从代码分支中以不合适的方式逃脱(删除手榴弹,按下按钮等),例如立即退出深层嵌套的条件和循环副作用。

另请注意,拱门不是时间机器;时间永远不会在故事中逆转自己,传说中的人物也不会穿过拱门。 (通过调用延续函数,不会颠倒破坏性赋值,堆内存的更改和其他副作用。)

练习:为要跟随的传说中的角色编写伪代码,这将导致正确执行的间谍计划。