为什么如果在 REPL 中逐行输入 Lisp 代码会执行,但如果在缓冲区中编译则不会执行?

Why does Lisp code execute if entered line by line in the REPL but not if compiled in the buffer?

我是 Lisper 菜鸟,正在尝试使用 Portacle Emacs 学习 Adam Tornhill 的 Lisp for the Web 教程。

我非常感谢解释为什么下面的设置代码会 运行 愉快地将 REPL 光标更改为 RETRO-GAMES> 如果在 REPL 中逐行输入但似乎没有如果在缓冲区中编译则工作。 (第一次编译时经常会报错说找不到快速加载包;REPL 游标永远不会改变。)

如果有人能阐明这对编译程序意味着什么,那就太好了。我正在努力理解直接编程到 REPL 而不是保存文件的价值。如果有关于完全编译程序“看起来像”的任何菜鸟级资源,就它是否包含下面的加载代码类型而言,link 会让我开心。谢谢。

(load "C:/Users/USER/portacle/all/quicklisp/quicklisp/setup.lisp")

(ql:quickload :cl-who)
(ql:quickload :hunchentoot)
(ql:quickload :parenscript)

(defpackage :retro-games
  (:use :cl :cl-who :hunchentoot :parenscript))
(in-package :retro-games)
(defclass game ()
  ((name :initarg :name)
   (votes :initform 0)))

要了解为什么这段代码是编译问题,请考虑编译器在编译包含它的文件时必须做什么,在 'cold' Lisp 映像中,这里的 'cold' 具体表示 'without Quicklisp loaded'。要看到这一点,请将其分成块。为了论证,我们假设文件编译器的工作方式是从文件中读取一个表单,编译它,然后将它写出。它确实必须以与我们的目的等效的方式工作,所以这里没问题。

第一块

(load "C:/Users/USER/portacle/all/quicklisp/quicklisp/setup.lisp")

load 是一个普通函数,对它的调用发生在文件的顶层:编译器将做的是编译代码,以便 加载文件时 这个 load 被调用了。编译器本身不会执行 load,因此 Quicklisp 不会在编译时加载。

第二块

(ql:quickload :cl-who)
(ql:quickload :hunchentoot)
(ql:quickload :parenscript)

这段代码有两个问题。在 cold lisp 中 QL 包不存在,因为之前的 load 没有在 compile-time 执行,所以 reader 在读取这些表格时会引发异常。编译到此停止。

如果 Lisp 不是冷的,特别是如果 QL 存在(所以,如果正在编译的 Lisp 图像确实加载了 Quicklisp到其中)然后编译器将简单地编写代码导致这三个 ql:quickload 调用发生在 load-time,并且现在不会加载这三个 QL 系统。

在口齿不清的情况下,您将无法超越这一步。在编译时加载了 Quicklisp 的 Lisp 中你会。

第三块

(defpackage :retro-games
  (:use :cl :cl-who :hunchentoot :parenscript))

只有在编译时加载 Quicklisp 的 Lisp 中才能到达此块。这是一个包定义,它必须在编译时生效。但是,在编译时,第二个块中的调用还没有发生:这个包定义引用了不存在的包。所以这是一个 compile-time 错误,编译将在此处停止。

如果进行编译的 Lisp 不仅加载了 Quicklisp,而且加载了三个 QL 系统,这将成功。

第四块

(in-package :retro-games)

这只会在 lisp 中实现,它在编译时同时加载了 Quicklisp 这三个 QL 系统。在那种情况下,它告诉编译器要在 retro-games 包中读取以下形式。否则永远达不到。

第五块

(defclass game ()
  ((name :initarg :name)
   (votes :initform 0)))

万一编译器走到这一步,这定义了一个名为 retro-games::game.

的 class

问题是什么

问题是编译器是一个编译器:它所做的是编译代码供以后执行。但是源代码的后面部分取决于加载的源代码的前面部分。

有两种方法可以解决这个问题。

如果您只编写非常少量的代码,那么您可以告诉编译器有些事情必须在编译时和加载时发生。你这样做的方法是 eval-when,你想要的特定咒语是这样的,对于第一块和第二块:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (load "C:/Users/USER/portacle/all/quicklisp/quicklisp/setup.lisp"))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload :cl-who)
  (ql:quickload :hunchentoot)
  (ql:quickload :parenscript))

请注意,您必须有两个单独的 eval-when 咒语,因为 QL 包需要由第一个生成,以便第二个可以被读取。

eval-when 的语义有点毛茸茸,但这个咒语会做你想做的事:它会告诉编译器加载这些东西并确保它们在加载时也是 运行 .

适用于较大系统的第二种处理方法是拥有一个或多个文件,这些文件为以后的文件构建 compile-time 环境,并编译 and在后面的文件之前加载,在像 ASDF 这样的东西的控制下。在这种情况下,您通常需要一些最小的 bootstrap 文件,它可能只需要确保加载 Quicklisp,然后要求 Quicklisp(以及 ASDF)编译和加载其他所有内容,并由这些工具负责依赖管理,所以根本没有 ql:quickloads。然而,如何设置这样的东西超出了这个答案!