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

标签 lisp scheme common-lisp continuations callcc

Scheme提供了一个原始的call-with-current-continuation,通常缩写为call/cc,它在ANSI Common Lisp规范中没有等效项(尽管有一些库尝试实现它们)。
有人知道做出不在ANSI Common Lisp规范中创建类似基元的决定的原因吗?

最佳答案

Common Lisp具有详细的文件编译模型,作为标准语言的一部分。该模型支持将程序编译为一个环境中的目标文件,并在另一个环境中将它们加载到镜像中。在Scheme中没有可比的。没有eval-whencompile-fileload-time-value或诸如什么是可外部化的对象之类的概念,编译后的代码中的语义如何与解释后的代码一致。 Lisp可以内联或不内联函数,因此基本上,您可以非常精确地控制重新加载已编译模块时发生的情况。
相比之下,直到最近对Scheme报告进行修订之前,Scheme语言对于将Scheme程序如何拆分为多个文件的话题完全保持沉默。没有为此提供功能或宏。查看6.6.4 System Interface下的R5RS。您所拥有的全部是一个非常宽松定义的load函数:

optional procedure: (load filename)

Filename should be a string naming an existing file containing Scheme source code. The load procedure reads expressions and definitions from the file and evaluates them sequentially. It is unspecified whether the results of the expressions are printed. The load procedure does not affect the values returned by current-input-port and current-output-port. Load returns an unspecified value.

Rationale: For portability, load must operate on source files. Its operation on other kinds of files necessarily varies among implementations.


因此,如果这是您对如何从模块构建应用程序的远见卓识,而超出此范围的所有细节留给实现者来解决,那么,当然是发明编程语言语义的极限。请部分注意“基本原理”部分:如果load定义为对源文件进行操作(所有其他行为均由实现者提供),那么它无非就是C语言中的像#include这样的文本包含机制,因此Scheme应用程序实际上,它只是一个文本主体,实际上是散布到load合并在一起的多个文本文件中。
如果要考虑向Common Lisp添加任何功能,则必须考虑它如何适合其详细的动态加载和编译模型,同时保留用户期望的良好性能。
如果您要考虑的功能需要全局的整个程序优化(系统需要查看所有结构的源代码),以便用户程序不会运行不佳(特别是不使用该功能的程序) ),那么它不会真正飞起来。
特别是关于延续的语义,存在一些问题。按照块作用域的通常语义,一旦我们离开作用域并执行清除操作,该操作就消失了。我们无法及时回到该范围并恢复计算。普通Lisp以这种方式很普通。我们具有unwind-protect构造,该构造在范围终止时执行无条件清理操作。这是with-open-file之类的功能的基础,它为块作用域提供了一个打开的文件句柄对象,并确保无论块作用域如何终止,都将其关闭。如果延续从该范围中转义,则该延续将不再具有有效文件。离开范围时,我们不能简单地不关闭文件,因为不能保证将使用延续。也就是说,我们必须假设范围实际上已被永久废弃,并及时清理资源。此类问题的创可贴解决方案是dynamic-wind,它使我们可以在进入和退出块作用域时添加处理程序。因此,当通过连续重新启动块时,我们可以重新打开文件。并且不仅重新打开它,而且实际上将流定位在文件中完全相同的位置,依此类推。如果流是在解码某些UTF-8字符的途中,我们必须将其置于相同状态。因此,如果Lisp有了延续,它们将被执行清除(不良集成)的各种with-构造破坏,否则这些构造将不得不获得更多毛茸茸的语义。
延续的替代方法。延续的某些用法是不必要的。本质上,通过关闭或重新启动可以获得相同的代码组织。另外,还有一个强大的语言/操作系统构造可以与延续竞争:线程。尽管延续的某些方面不是由线程很好地建模的(更不用说它们不会在代码中引入死锁和竞争条件),但与线程相比,它们也有缺点:例如缺乏利用多个处理器的实际并发性,或者优先排序。用延续表达的许多问题几乎可以用线程表达。例如,延续让我们编写了一个递归下降解析器,它看起来像一个流对象,在解析时只返回渐进结果。该代码实际上是递归下降解析器,而不是模拟一个的状态机。线程让我们做同样的事情:我们可以将解析器放入包裹在“ Activity 对象”中的线程中,该“ Activity 对象”具有一些“获取下一件事”方法,可以从队列中提取内容。作为线程解析器,它没有返回连续性,而只是将对象放入队列(并可能阻塞其他线程以将其删除)。通过恢复该线程来提供执行的继续;它的线程上下文是延续。并非所有的线程模型都遭受竞争条件的折磨(同样多)。例如协作线程,一次运行一个线程,并且仅当线程对线程内核进行显式调用时,才可能发生线程切换。 Common Lisp的主要实现已经有轻量级的线程(通常称为“进程”)了几十年,并已逐步向具有多处理支持的更复杂的线程发展。对线程的支持减少了继续操作的需要,并且具有更高的实现优先级,因为没有线程支持的语言运行时在技术上处于劣势:无法充分利用硬件资源。

关于lisp - 为什么Common Lisp中不存在原始的 `call-with-current-continuations`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16651843/

相关文章:

mongodb - 使用 cl-mongo 时如何表达 $or

lisp - 如何编译使用 cl-ppcre 的 clisp 程序?

recursion - lisp中的多级递归

scheme - 如果事先不了解 Scheme 的底函数和舍入函数,如何完成 SICP 练习 1.45(计算 n 次方根函数)?

common-lisp - 如何正确等待n秒

Emacs:键绑定(bind)到匿名函数的性能

scheme - 句子输出是垂直的,每个单词之间有换行符

multithreading - 如何从异常处理程序中产生线程的当前延续

lisp - from-end 不能像我预期的那样工作

tree - 使用搜索找到树中最深的节点,然后移动它