并非所有实现call/cc
完全相同,但希望这个答案可以适用于所有常见的变体,包括球拍,没有什么麻烦。这个故事其实是根据c
内置于Unlambda http://www.madore.org/~david/programs/unlambda/#callcc.
呼叫/抄送的隐喻
你是一位超级英雄考古学家,正在挖掘古老的玛雅遗址。您会在优雅的拱门中发现一处构造精美、保存完好的石门。但这只是一个门,是侧着的;它附近没有任何一堵墙,它似乎也不是墙的一部分。您的工作人员可以读取它的能量读数,因此您可以将其完好无损地运回实验室进行研究。
在您的实验室中,一个大钟挂在现在直立的拱门正前方的墙上,您将其放置在房间中央附近。在检查拱门时,您会穿过它。
4:17 PM
穿过拱门后,您会惊奇地发现,您手里拿着一个立方体盒子,它大到足以在里面放一本书,有一个盖子和一个发光按钮。你不记得拿起过这样一个盒子,也不记得以前见过它。你叫来你的助手,询问盒子是从哪里来的。助手不知道。如果您放下盒子,按钮就会停止发光。你再次拿起它,按钮再次发光。只有当你握住它时它才会发光;如果助手拿起盒子,它就不会发光。在百灵鸟中,您将一个回形针放入盒子中,合上盖子,然后按下按钮。
4:23 PM
穿过拱门后,您惊讶地发现您手里拿着一个回形针。你不记得捡起过它。你的助手在房间里,目瞪口呆地看着你。你不记得你的助手走进过房间。您的时钟似乎也快了几分钟。
“刚刚发生了什么?”助手问道。
“我刚刚走过拱门,”你说,并没有真正理解这个问题。
“不,你没有!那盒子怎么了?”助手说。
“你在说什么?”你说,越来越生气了。
...
一旦整个情况发生,并且您了解了这个拱门的作用,您决定做一些大胆的事情。您大声读出当前时间,然后穿过拱门。
5:30 PM
从拱门出来,你手里拿着一个空盒子。观察到时间如您所料,为下午 5:30,您将一张便利贴贴在标有“#1
。你把盒子放在桌子上,大声读出当前时间,然后再次穿过拱门。
5:31 PM
从拱门出来,你手里拿着一个空盒子。观察到时间如您所料,为下午 5:31,您将一张便利贴贴在标有“#2 - use to forget
。你放置框 #2inside盒子#1(当你这样做时,它会缩小到其大小的一小部分;看起来这些盒子就是为此而设计的)。
Being a 超级英雄考古学家,然后你适当地装备自己,闯入你最不喜欢的压迫政权的外国大使馆,突破它的金库并窃取一些严格保守的国家机密的纸质副本。香港动作片里的东西,子弹和拳头飞舞。你把自己关在金库里,在他们的守卫找到你之前争取宝贵的时间。从你的包里拿出1号盒子,把文件塞进去(和2号盒子一起,但不是里面),关闭1号盒子,就在金库门被吹开的时候,你微笑着,拉了拉扣住别针,放下手榴弹,然后按下按钮。
9:45 PM
从拱门出来,你拿着一个空盒子,上面贴着标签#2 - use to forget
以及包含您最不喜欢的压迫政权的敏感国家机密的文件。您还注意到时间不是您预期的下午 5:30,而是晚上 9:45,因此您没有继续执行预期的闯入计划。你坐下来,把文件的内容记下来,一旦你确定你已经完全记住了它们,你就把文件烧进垃圾桶。现在,您大声读出当前时间并穿过拱门。
2:00 AM
从拱门出来,你手里拿着一个空盒子。观察到当前时间如您所料,为凌晨 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 AM
从拱门出来,你拿着一个空盒子,上面贴着标签#3 - use to remember
,以及您亲笔写的注释。时间比预计的下午 5:31 晚很多,因此您没有继续执行预定的闯入计划。按照你给自己的指示,你再次穿过拱门,获得一个你贴上标签的新盒子#4 - use to forget
。你按照笔记中的指示打电话给警察,但不知道为什么,几天后你在新闻中听到一个国际间谍团伙在你的家乡被捣毁。
任务完成! (你假设。)从这一点开始,你可以选择了解信息本身,但不知道你用它做了什么;或者不知道该信息,但知道它是如何使用的。
最后,这个美妙的工具是有代价的。当你在各种活动中继续依赖拱门时,你必须知道你将永远分裂你的生活,并且除非通过在你的许多替代自我之间发送消息,否则这些碎片永远无法统一。按钮盒的一次使用可能会导致许多珍贵记忆的不可挽回的损失,这可能是一种策略,也可能是一个严重和悲剧性的错误。
解释
穿过拱门代表着call/cc
手术。这样做会创建一个新的按钮框,它代表一个延续函数。将东西放入框中表示将参数传递给延续函数。按下方框上的按钮代表调用延续函数。
按下盒子上的按钮(调用延续函数)会使故事字符(带有变量的调用堆栈)恢复到创建盒子(延续)时的确切状态,即创建该盒子时的状态。call/cc
最初完成。传递给延续的参数成为原始的结果值call/cc
堆栈恢复后;这就是为什么按下按钮后,故事中的角色拿着的不是盒子,而是盒子里的东西。
盒子(延续)可以被封装(互相传递),允许使用call/cc
作为实现协同例程、状态机和其他高级构造的原语。
延续还可以用来以不方便的方式轻松逃离代码分支(“扔下手榴弹,按下按钮”),例如立即退出深层嵌套的条件和导致副作用的循环。
另请注意,拱门不是时间机器;它是一个时间机器。时间在故事中永远不会逆转,故事人物走过拱门的外部事物也不会逆转。 (破坏性赋值、对堆内存的更改以及其他副作用不会通过调用延续函数来逆转。)
练习:为要遵循的故事人物编写伪代码,这将导致正确执行所描述的间谍计划。