为什么Common Lisp中不存在原始的`call-with-current-continuations`

时间:2013-05-20 14:35:02

标签: lisp scheme common-lisp continuations callcc

最近我一直在研究Scheme和Common Lisp之间关于这两种语言对延续的方法的区别。

我注意到Common Lisp方法比Scheme方法更保守。

此外,Scheme提供了一个原始call-with-current-continuation,通常缩写为call/cc,它在ANSI Common Lisp规范中没有等价物(尽管有一些库试图实现它们)。

是否有人知道为什么不在ANSI Common Lisp规范中创建类似原语的决定?

提前致谢。

4 个答案:

答案 0 :(得分:21)

Common Lisp有一个详细的文件编译模型作为标准语言的一部分。该模型支持在一个环境中将程序编译为目标文件,并将它们加载到另一个环境中的图像中。 Scheme没有可比性。没有eval-when,或compile-fileload-time-value或类似什么是可外化对象的概念,编译代码中的语义必须与解释代码一致。 Lisp有一种方法可以内联或不内联函数,因此基本上你可以非常精确地控制重新加载编译模块时会发生什么。

相比之下,在最近修订Scheme计划之前,Scheme语言对于如何将Scheme程序分解为多个文件的主题完全保持沉默。没有为此提供任何功能或宏。查看6.6.4 System Interface下的R5RS。你所拥有的只是一个非常宽松的load函数:

  

可选程序:(加载文件名)

     

Filename应该是一个字符串,用于命名包含Scheme源代码的现有文件。加载过程从文件中读取表达式和定义,并按顺序对它们进行求值。未指定是否打印表达式的结果。加载过程不会影响current-input-port和current-output-port返回的值。 Load返回一个未指定的值。

     

基本原理:为了便于携带,加载必须对源文件进行操作。它对其他类型文件的操作必然因实现而异。

因此,如果这是关于如何从模块构建应用程序的愿景的范围,并且除此之外的所有细节都留给实现者来解决,当然天空是发明编程语言语义的限制。请注意部分基本原理部分:如果load被定义为对源文件进行操作(其他所有内容都是实现者的奖励)那么它只不过是一个文本包含机制,如#include C语言,因此Scheme应用程序实际上只是一个文本体,它被物理地分散到由load拉到一起的多个文本文件中。

如果您正在考虑向Common Lisp添加任何功能,您必须考虑它如何适应其详细的动态加载和编译模型,同时保留用户期望的良好性能

如果您正在考虑的功能需要全局,整个程序优化(系统需要查看所有内容的结构源代码),以便用户可以使用。程序运行不好(特别是那些不使用该功能的程序)然后它真的不会飞。

特别是关于延续的语义,存在问题。在块作用域的通常语义中,一旦我们离开作用域并执行清理,就会消失;我们不能及时回到那个范围并恢复计算。 Common Lisp就是普通的。我们有unwind-protect构造,当作用域终止时,它执行无条件的清理操作。这是with-open-file等功能的基础,它为块作用域提供了一个打开的文件句柄对象,并确保无论块作用域如何终止都关闭它。如果延续从该范围中逃脱,则该延续不再具有有效文件。当我们离开范围时,我们不能简单地关闭文件,因为无法保证将继续使用延续;也就是说,我们必须假设范围实际上是永远被放弃并及时清理资源。针对此类问题的创可贴解决方案是dynamic-wind,它允许我们在进入和退出块范围时添加处理程序。因此,当通过延续重新启动块时,我们可以重新打开该文件。而且不仅重新打开它,而且实际上将流定位在文件中的完全相同的位置,依此类推。如果流是解码某些UTF-8字符的一半,我们必须将它置于相同的状态。因此,如果Lisp得到延续,要么它们会被执行清理(差集成)的各种with-构造打破,否则这些构造必须获得更多毛茸茸的语义。

还有替代方案。连续性的一些用法是非必要的。通过闭包或重启可以获得基本相同的代码组织。此外,还有一个强大的语言/操作系统结构可以与延续竞争:即线程。虽然continuation的方面没有被线程很好地建模(更不用说它们没有在代码中引入死锁和竞争条件),但与线程相比它们也有缺点:比如缺乏使用多个处理器的实际并发性,或者优先级。用延续表达的许多问题几乎可以用线程表达。例如,continuation让我们编写一个递归下降的解析器,它看起来像一个类似于流的对象,它在解析时只返回渐进结果。代码实际上是递归下降解析器,而不是模拟一个的状态机。线程让我们做同样的事情:我们可以将解析器放入一个包含在"活动对象"中的线程中,它有一些"得到下一个东西"从队列中提取东西的方法。作为线程解析器,它不是返回延续,而是将对象抛出到队列中(并且可能阻塞其他线程以删除它们)。通过恢复该线程来提供继续执行;它的线程上下文是延续。并非所有线程模型都受到竞争条件的影响(同样多);例如,协作线程,一次运行一个线程,并且只有当线程对线程内核进行显式调用时才可能发生线程切换。几十年来,主要的Common Lisp实现都有轻量级线程(通常称为"进程"),并逐渐转向更复杂的线程,并提供多处理支持。对线程的支持减少了对continuation的需求,并且是一个更大的实现优先级,因为没有线程支持的语言运行时处于技术劣势:无法充分利用硬件资源。

答案 1 :(得分:7)

这就是Common Lisp的设计师之一Kent M. Pitman在这个主题上所说的话:from comp.lang.lisp

答案 2 :(得分:3)

Scheme的设计基于使用函数调用来替换大多数常见的控制结构。这就是Scheme需要消除尾部调用的原因:它允许将循环转换为递归调用,而不会耗尽堆栈空间。其基本方法是延续传递方式

Common Lisp更实用,教学更少。它没有规定实施策略,并且不需要继续实施它。

答案 3 :(得分:3)

Common Lisp是对几种实用(应用)Lisp(因此“Common”)的标准化努​​力的结果。 CL面向现实生活中的应用程序,因此它具有更多“特定”功能(如handler-bind)而不是call/cc

Scheme被设计为用于教授CS的小型干净语言,因此它具有可用于实现其他工具的基础call/cc

另见Can call-with-current-continuation be implemented only with lambdas and closures?

相关问题