实用zhlisp编程02:REPL简介
Category:中文学习第2章 周而复始:REPL简介
本章将学习如何设置编程环境并编写第一个 Common Lisp 程序。我们将使用由 Matthew Danish 和 Mikel Evins 开发的安装便利的 Portacle 环境,它封装了带有 Emacs 强大且对 Lisp 较为友好的文本编辑器的 Common Lisp 实现1,以及 SLIME(Superior Lisp Interaction Mode for Emacs)——构建在 Emacs 之上的 Common Lisp 开发环境。
上述组合提供了一个全新的 Common Lisp 开发环境,可以支持增量、交互式的开发风格——这是 Lisp 编程所特有的。SLIME 环境提供了一个完全统一的用户接口,跟你所选择的操作系统和 Common Lisp 实现无关。为了能有一个具体的开发环境来进行讲解,我将使用这个Portacle 环境。而那些想要其他开发环境的人,无论是使用某些商业 Lisp 供应商的图形化集成开发环境(IDE)还是基于其他编辑器的环境,在应用本书的内容时应该都不会有较大的困难。2
2.1 选择一个Lisp实现
首先要做的事是选择一个 Lisp 实现。这对那些曾经使用诸如 Perl、Python、Visual Basic (VB)、C# 和 Java 语言的人来说可能有些奇怪。Common Lisp 和这些语言的区别在于,Common Lisp 是标准化的——既不像 Perl 和 Python 那样是由善良的专制者所控制的某个实现,也不像 VB、C# 和 Java 那样是由某个公司控制的公认的实现。任何打算阅读标准并实现该语言的人都可以自由地这样做。更进一步,对标准的改动必须在标准化组织美国国家标准化协会(ANSI)所控制的程序下进行。这一设计可以避免任何个体(例如某个厂商)3 任意修改标准。这样,Common Lisp 标准就是 Common Lisp 供应商和 Common Lisp 程序员之间的一份协议。这份协议告诉你,如果所写的程序按照标准里描述的方式使用了某些语言特性,那么就可以指望程序在任何符合标准的实现里也能产生同样的行为。
另一方面,标准可能并没有覆盖程序所涉及的每个方面——某些方面故意没有定义,允许实现者在这些领域里继续探索:特定的语言风格尚未找到最佳的支持方式。因此每种实现都提供了一些依托并超越标准所规定范围的特性。根据你正打算进行的编程类型,有时选择一种带有所需的额外特性的特定实现也是合理的。另一方面,如果我们正在向其他人交付Lisp源代码,例如库,那么你将需要尽可能地编写可移植的 Common Lisp。对于编写那些大部分可移植的,但需要用到标准中没有定义的功能的代码,假如所要编写的代码大部分可以移植,但其中要用到一些标准未曾定义过的功能。Common Lisp 提供的灵活方式可以编写出完全依赖于特定实现中某些特性的代码。你将在第 15 章看到这样的代码,在那里我们将开发一个简单的库来消除不同 Lisp 实现在对待文件名上的区别。
但就目前而论,一个 Lisp 实现最重要的特点应该在于它能否运行在我们所喜爱的操作系统上。Franz 的 Allegro Common Lisp 开发者们提供了一个可用于本书的试用版产品,它能够在 Linux、Windows 和 Mac OS X 上运行。试图寻找开源实现的人们有几种选择。SBCL(Steel Bank Common Lisp)是一个高质量的开源实现,它将程序编译成原生代码并且可以运行在广泛的 Unix 平台上,包括 Linux 和 Mac OS X。SBCL 4来源于 CMUCL5(CMU Common Lisp)——最早由卡内基梅隆大学开发的一种 Common Lisp,并且像 CMUCL 那样,大部分源代码都处于公共域(public domain),只有少量代码采用伯克利软件分发(BSD)风格的协议。CMUCL 本身也是个不错的选择,尽管 SBCL 更容易安装并且现在支持 21 位 Unicode。6而对于 Mac OS X 用户来说,Clozure Common Lisp (CCL)是一个极佳的选择,它可编译到机器码、支持线程,并且可以跟 Mac OS X 的 Carbon 和 Cocoa 工具箱很好地集成。还有其他的开源和商业实现,参见第 32 章以获取更多相关资源的信息。
除非特别说明,本书中的所有 Lisp 代码都应该可以工作在任何符合标准的 Common Lisp 实现上,而 SLIME 通过为我们提供一个与 Lisp 交互的通用接口也可以消除不同实现之间的某些差异。本书里给出的程序输出来自运行在 GNU/Linux 上的 Allegro 平台。在某些情况下,其他 Lisp 可能会产生稍有不同的错误信息或调试输出。
2.2 安装和运行Portacle
由于 Portacle 软件包可以让新 Lisp 程序员在一流的 Lisp 开发环境上近乎无痛起步,因此你需要做的就是根据你的操作系统和喜爱的 Lisp 平台从第 32 章中列出的 Portacle 的 https://Portacle.github.io/ 站点上获取相应的安装包,然后按照安装说明提示来操作即可。
由于 Portacle 使用 Emacs 作为其编辑器,因此你至少得懂一点儿它的使用方法。最好的 Emacs 入门方法也许就是跟着它内置的向导走一遍。要想启动这个向导,选择帮助菜单的第一项 Emacs tutorial 即可。或者按住 Ctrl 键,输入 h
,然后放开 Ctrl 键,再按 t
。大多数 Emacs 命令都可以通过类似的组合键来访问。由于组合键用得如此普遍,因而 Emacs用户使用了一种记号来描述组合键,以避免经常书写诸如 “按住 Ctrl 键,输入 h
,然后放开 Ctrl 键,再按 t
” 这样的组合。需要一起按的键即所谓的键和弦,用连接号(-
)连接;顺序按 下的键(或者键和弦键)用空格分隔。在一个键和弦里,C
代表 Ctrl 键而 M
代表 Meta 键(也就是 Alt 键)。这样,我们可以将刚才描述的启动向导的按键组合直接写成:C-h t
。
向导里还描述了其他有用的命令以及启动它们的组合键。Emacs 还提供了大量在线文档,可以通过其内置的超文本浏览器 Info 来访问。阅读这些手册只需输入 C-h i
。这个 Info 系统也有其自身的向导,可以简单地在阅读手册时按下 h
来访问。 最后,Emacs 提供了好几种获取帮助信息的方式,全部绑定到了以 C-h
开头的组合键上。输入 C-h ?
就可以得到一个完整的列表。除了向导以外,还有两个最有用的帮助命令一个是 C-h k
,它可以告诉我们输入的任何组合键所对应的命令是什么;另一个是 C-h w
,它可以告诉我们输入的命令名字所对应的组合键是什么。
对于那些拒绝看向导的人来说,有个至关重要的 Emacs 术语不得不提,那就是缓冲区(buffer)。当使用 Emacs 时,你所编辑的每个文件都将被表示成不同的缓冲区,在任一时刻只有一个缓冲区是 “当前使用的”。当前缓冲区会接收所有的输入——无论是在打字还是调用任何命令。缓冲区也用来表示与 Common Lisp 这类程序的交互。因此,你将用到的一个常见操作就是 “切换缓冲区”,就是说将一个不同的缓冲区设置为当前缓冲区,以便可以编辑某个特定的文件或者与特定的程序交互。这个命令是 switch-to-buffer
,对应的组合键是 C-x b
,使用时将提示在 Emacs 框架的底部输入缓冲区的名字。当输入一个缓冲区的名字时,按 Tab 键将在输入的字符基础上对其补全,或者显示一个所有可能补全方式的列表。该提示同时推荐了一个默认缓冲区,你可以直接按回车键(Return)来选择它。也可以通过在缓冲区菜单里选择一个缓冲区来切换。
在特定的上下文环境中,其他组合键也可用于切换到特定的缓冲区。例如,当编辑 Lisp 源文件时,组合键 C-c C-z
可以切换到与 Lisp 进行交互的那个缓冲区。
2.3 放开思想:交互式编程
当启动 Portacle 时,应该可以看到一个带有类似下面提示符的缓冲区:
CL-USER>
这是 Lisp 的提示符。就像 Unix 或 DOS shell 提示符那样,在 Lisp 提示符的位置上输入表达式可以产生一定的结果。尽管如此,Lisp 读取的是 Lisp 表达式而非一行 shell 命令,按照 Lisp 的规则来对它求值,然后打印结果。接着它再继续处理你输入的下一个表达式。正是由于这种无休止的读取、求值和打印的周期变化,因此它被称为读-求值-打印循环(read-eval-print loop),简称 REPL。它也被称为顶层(top-level)、顶层监听器(top-level listener)或 Lisp 监听器。
借助 REPL 提供的环境就可以定义或重定义诸如变量、函数、类和方法等程序要素,求值任何 Lisp 表达式,加载含有 Lisp 源代码或编译后代码的文件,编译整个文件或者单独的函数,进入调试器,单步调试代码,以及检查个别 Lisp 对象的状态。
所有这些机制都是语言内置的,可以通过语言标准所定义的函数来访问。如果有必要,你只需使用 REPL 和一个知道如何正确缩进 Lisp 代码的文本编辑器,就可以构建出一个相当合理的编程环境来。但如果追求真正的 Lisp 编程体验,则需要一个像 SLIME 这样的环境,它可以同时通过 REPL 和编辑源文件时与 Lisp 进行交互。 例如,没有必要把一个函数定义从源文件里复制并粘贴到 REPL 里,也不必因为改变了一个函数就把整个文件重新加载。Lisp 环境应该允许编译单独的表达式和直接来自编辑器的整个文件或对其求值。
2.4 体验REPL
为了测试 REPL,需要一个可以被读取、求值和打印的 Lisp 表达式。最简单类型的 Lisp 表达式是一个数。在 Lisp 提示符下,可以输入 10
接着敲回车键,然后看到类似下面的东西:
CL-USER> 10 10
第一个 10
是你输入的。Lisp 读取器,即 REPL 中的 R,读取文本 “10” 并创建一个代表数字 10 的 Lisp 对象。这个对象是一个自求值(self-evaluating)对象,也就是说当把它送给求值器,即 REPL 中的 E 以后,它将对其自身求值。这个值随后被送到打印器里,打印出只有 10 的那行来。整个过程看起来似乎是费了九牛二虎之力却回到了原点,但如果你给了 Lisp 更有意义的信息,那么事情就变得有意思一些了。比如说,可以在 Lisp 提示符下输入 (+ 2 3)
。
CL-USER> (+ 2 3) 5
小括号里的东西构成了一个列表,上述列表包括三个元素:符号 +
,以及数字 2 和 3。一般来说,Lisp 对列表求值的时候会将第一个元素视为一个函数的名字,而其他元素作为即将求值的表达式则形成了该函数的实参。在本例里,符号 +
是加法函数的名字。2 和 3 对自身求值后被传递给加法函数,从而返回了 5。返回值 5 被传递给打印器从而得以输出。Lisp 也可能以其他方式对列表求值,但我们现在还没必要讨论它。但从 你好,世界
开始。
2.5 Lisp风格的“你好,世界”
没有“你好,世界
”程序的编程书籍是不完整的。事实上,想让 REPL 打印出“你好,世界
”再简单不过了。
CL-USER> "你好,世界" "你好,世界"
其工作原理是,因为字符串和数字一样,带有 Lisp 读取器可以理解的字面语法并且是自求值对象:Lisp 读取双引号里的字符串,求值的时候在内存里建立一个可以对自身求值的字符串对象,然后再以同样的语法打印出来。双引号本身不是在内存中的字符串对象的一部分——它们只是语法,用来告诉读取器读入一个字符串。而打印器之所以在打印字符串时带上它们,则是因为其试图以一种读取器可以理解的相同语法来打印对象。
尽管如此,这还不能算是一个“你好,世界
”程序而更像是一个“你好,世界
”值。
向真正的程序迈进一步的方法是编写一段代码,其副作用可以将字符串“你好,世界
”打印到标准输出。Common Lisp 提供了许多产生输出的方法,但最灵活的是格式 函数。格式 接收变长参数,但是只有两个必要的参数,分别代表着发送输出的位置以及字符串。在下一章里你将看到这个字符串是如何包含嵌入式指令,以便将其余的参数插入到字符串里的,就像 printf
或者 Python 的 string-%
那 样。只要字符串里不包含一个 ~
,那么它就会被原样输出。如果将 t
作为第一个参数传入,那么它将会发送其输出到标准输出。因此,一个将输出“你好,世界
”的 格式 表达式应如下所示:
CL-USER> (格式 t "你好,世界") 你好,世界 NIL
关于 格式 表达式的结果,需要说明的一点是紧接着“你好,世界
”输出后面那行里的 NIL。那个 NIL 是 REPL 输出的求值 格式 表达式的结果。(NIL 是 Lisp 版本的逻辑假和空值。更多内容见第 4 章。)和目前我们所见到的其他表达式不同的是,格式 表达式的副作用(在本例中是打印到标准输出)比其返回值更有意义。但 Lisp 中的每个表达式都会求值出某些结果。9
尽管如此,你是否已经写出了一个真正的“程序”呢?恐怕仍有争议。不过你离目标越来越近了。而且你正在体会 REPL 所带来的自底向上的编程风格:可以试验不同的方法,然后从已经测试过的部分里构建出一个解决方案来。现在你已经写出了一个简单的表达式来做你想要的事,剩下的就是将其打包成一个函数了。函数是 Lisp 的基本程序构造单元,可以用类似下面这样的 函数 表达式来定义:
CL-USER> (函数 你好-世界 () (格式 t "你好,世界")) 你好-世界
函数 后面的 你好,世界
是这个函数的名字。在第 4 章里我们将看到究竟哪些字符可以在名字里使用,现在我们暂时假设包括 -
在内的很多在其他语言里非法的字符在 Common Lisp 里都是合法的。像 你好,世界
这种用连字符而不是下划线(你好,世界)
或是内部大写(你好,世界)
来形成复合词的方法,是标准的 Lisp 风格——更不用提那接近正常英语的排版了。名字后面的 ()
是形参列表, 在本例中为空是因为该函数不带参数。其余的部分是函数体。
表面上看,这个表达式和你目前见到的所有其他表达式一样,只是另一个被 REPL 读取、求值和打印的表达式。这里的返回值是你所定义的函数名。 10但是和 格式 表达式一样,这个表达式的副作用比其返回值更有用。但与 格式 表达式所不同的是,它的副作用是不可见的:当这个表达式被求值的时候,一个不带参数且函数体为 (格式 t "你好,世界")
的新函数会被创建出来并被命名为 你好-世界
。
一旦定义了这个函数,你就可以像这样来调用它:
CL-USER> (你好-世界) 你好,世界 NIL
你将看到输出和直接对 格式 表达式求值时是一样的,包括 REPL 打印出的 NIL 值。Common Lisp 中的函数自动返回其最后求值的那个表达式的值。
2.6 保存工作成果
你可能会争辩说这就是一个完整的“你好,世界
”程序了。但还有一个问题。如果退出 Lisp 然后重启,函数定义将会丢失。这么好的一个函数,你可能会想要保存下来。
而这很简单。只需创建一个文件,然后把定义保存在里面即可。在 Emacs 中可以通过输入 C-x C-f
来创建一个新文件,然后根据 Emacs 的提示输入文件的名字。文件保存的位置并不重要。Common Lisp 源文件习惯上带有 .lisp
扩展名,尽管有些人用 .cl
来代替。
一旦创建了文件,就可以向其中写入之前在 REPL 里输入过的定义。需要注意的是,在输入了开放括号和单词函数 以后,在 Emacs 窗口的底部,SLIME 将会提示它所期待的参数。具体的输出将取决于所使用的具体 Common Lisp 实现,但其形式可能会如下所示:
(函数 名字 形参列表 &rest body)
在开始输入每一个新的列表元素时,这个信息就会消失,但当每次输入了空格以后,它又会重新出现。在文件中输入这个定义的时候,你可能选择将函数从形参列表那里打断成两行。如果输入回车然后按 Tab 键,SLIME 将自动把第二行缩进到合适的位置,如下所示:11
(函数 你好-世界 () (格式 t "你好,世界"))
SLIME 也会帮助匹配括号——当输入了闭合括号时,它将闪烁显示对应的开放括号。或者也可以输入 C-c C-q
来调用命令 slime-close-parens-at-point
,它将插入必要数量的闭合括号以匹配当前的所有开放括号。
可用几种方式将这个定义输入到 Lisp 环境中。最简单的是,当光标位于 函数 定义内部的任何位置或者刚好在其后面时,输入 C-c C-c
,这将启动 slime-compile-defun
命令,将当前定义发给 Lisp 进行求值并编译。为了确认这个过程有效,你可以对 你好-世界
作些修改,重新编译它然后回到 REPL,使用 C-c C-z
或者 C-x b
再次调用它。例如,你可以使其更合乎语法。
(函数 你好-世界 () (格式 t "你好,世界!"))
接下来,用 C-c C-c
进行重新编译,然后输入 C-c C-z
来切换到 REPL 试一下新版本。
CL-USER> (你好-世界) 你好,世界! NIL
你将可能需要保存工作文件。在 你好
.lisp
缓冲区里,输入 C-x C-s
可以启动 Emacs 命令 save-buffer
。
现在尝试从源文件中重新加载这个函数,这需要退出并重启 Lisp 环境。执行退出操作可以使用一个 SLIME 快捷键:在 REPL 中输入一个逗号。在 Emacs 窗口底部,将提示你输入一个命令。输入 quit
(或 sayoonara
),然后按回车。这将退出 Lisp 并且关闭所有 SLIME 创建的缓冲区,包括 REPL 缓冲区。 现在用 M-x slime
重启 SLIME。
可以顺便试试直接调用 你好-世界
。
CL-USER> (你好-世界)
此时 SLIME 将弹出一个新的缓冲区并带有类似下面的内容:
Error: Undefined operator 你好-世界 in form (你好-世界).
1 (continue) Try invoking 你好-世界 again.
2 Return some values from the form (你好-世界).
3 Try invoking something other than 你好-世界 with the same arguments.
4 Set the symbol-function of 你好-世界 to another function.
5 Set the macro-function of 你好-世界 to another function.
6 (abort) Return to level 0.
7 Return to top loop level 0.Type :b for backtrace or :c <option number> to proceed.
Type :bug-form “<subject>” for a bug report template or 😕 for other options.
天哪!发生了什么事?原来,你试图调用了一个不存在的函数。不过尽管输出了很多东西,Lisp 实际上正在很好地处理这一情况。跟 Java 或者 Python 不同,Common Lisp 不会只是放弃——抛出一个异常并从栈上退回,而且它也绝对不会仅仅因为调用了一个不存在的函数就开始做核心转储(core dump)。事实上 Lisp 会把你带进调试器。
当身处调试器之中时,你仍然拥有对 Lisp 的完全访问权限,所以可以通过求值表达式来检查我们程序的状态,甚至可以直接修复一些东西。不过目前先别担心它们。直接输入 q
或 :a
退出调试器,然后回到 REPL 里。调试器缓冲区将会消失,而 REPL 将显示:
CL-USER> (你好-世界) ; 评估中止 CL-USER>
相比直接中止调试器,在它里面显然可以做更多的事情——例如我们将在第 19 章里看到调试器是如何与错误处理系统集成在一起的。尽管如此,目前最重要的是要知道总是可以通过按 q
或 :a
来退出它并回到 REPL 里。
回到 REPL 里可以再试一次。问题在于 Lisp 不知道 你好-世界 的定义,因此你需要让 Lisp 知道我们保存在 你好.lisp
文件中的定义。有几种方式可以做到这一点。可以切换回含有那个文件的缓冲区(使用 C-x b
在其提示时输入 你好.lisp
),然后就像之前那样用 C-c C-c
重新编译那个定义。或者你可以加载整个文件,当文件里含有大量定义时,这将更加便利,在 REPL 里像这样使用 加载 函数:
CL-USER> (加载 "你好.lisp") ; 加载/home/peter/my-lisp-programs/
你好.lisp T
那个 T 表示文件被正确加载了。 使用加载 加载一个文件,本质上等价于以文件中出现的顺序在 REPL 下逐个输入每一个表达式,因此在调用了加载 之后,你好-世界
就应该有定义了:
CL-USER> (你好-世界) 你好,世界! NIL
另一种加载文件中有用定义的方法是先用 编译 命令编译,然后再用加载 命令加载编译后产生的文件,也就是 FASL 文件——快速加载文件(fast-load file)的简称。编译 将返回 FASL 文件的名字,所以我们可以在 REPL 里像下面这样进行编译和加载:
CL-USER> (加载 (编译 "你好.lisp")) ;;; 编译文件 你好.lisp ;;; 写入fasl文件 你好.fasl ;;; FASL写入完成 ; 快速加载 /home/peter/my-lisp-programs/你好.fasl T
SLIME 还提供了不需要使用 REPL 来加载和编译文件的支持。在一个源代码缓冲区时,你可以使用 C-c C-l
调用命令 slime-load-file
来加载文件。Emacs 将会提示你给出要加载的文件名,同时将当前的文件名作为默认值,直接回车就可以了。或者可以输入 C-c C-k
来编译并加载那个当前缓冲区所关联的文件。在一些 Common Lisp 实现里,对代码进行编译将使其速度更快一些;在其他实现里可能不会,因为它们总是编译所有东西。
这些内容应该足够给你一个关于 Lisp 编程如何工作的大致印象了。当然我还没有涉及所有的技术和窍门,但你已经见到其本质要素了——通过与 REPL 的交互来尝试一些东西,加载和测试新代码,调整和调试它们。资深的 Lisp 黑客们经常会保持一个 Lisp 映像日复一日地运行,不断地添加、重定义和测试他们的程序。
同样地,甚至当 Lisp 程序被部署以后,往往仍有一种方式可以进入 REPL。第 26 章介绍如何使用 REPL 和 SLIME 来跟正在运行一个 Web 服务器的 Lisp 进行交互,同时它还在伺服 Web 页面。甚至有可能用 SLIME 连接到运行在另一台不同机器里的 Lisp,从而允许你(比如说)像本地环境那样去调试一个远程服务器。
一个更加令人印象深刻的案例是 1998 年发生在 NASA 的 Deep Space 1 号任务中的远程调试。在宇宙飞船升空半年以后,一小段 Lisp 代码正准备控制飞船以进行为期两天的一系列实验。不幸的是,代码里的一个难以察觉的竟态条件逃过了地面测试期间的检测并且已经升空了。当这个错误在距地球一亿英里外的地方出现时,地面团队得以诊断并修复了运行中的代码,使得实验顺利地完成。 一个程序员如此描述了这件事:
调试一个运行在一亿英里之外且价值一亿美元硬件上的程序是件有趣的经历。一个运行在宇宙飞船上的读-求值-打印循环,在查找和修复这个问题的过程中,真是无价之宝啊。
你还没有准备好将任何 Lisp 代码发送到太空,不过在接下来一章里,你就将亲身参与编写一个比 “你好,世界” 更有趣一点儿的程序了。
1 Emacs的高级Lisp交互模式
2如果您之前遇到过使用过Emacs的糟糕体验,那么您应该将盒中的Lisp视为一个IDE,它恰好使用类似Emacs的编辑器作为其文本编辑器; 没有必要成为编程Lisp的Emacs大师。但是,使用具有一些基本Lisp感知功能的编辑器对Lisp进行编程会更有趣。至少,您需要一个可以自动匹配的编辑器 ()为你而且知道如何自动缩进Lisp代码。因为Emacs本身主要用Lisp方言Elisp编写,所以它对编辑Lisp代码有相当多的支持。Emacs也深深植根于Lisp的历史和Lisp黑客的文化:最初的Emacs及其前身TECMACS和TMACS是由Lispers在麻省理工学院(MIT)编写的。Lisp机器上的编辑器是完全用Lisp编写的Emacs版本。前两个Lisp Machine Emacs,遵循黑客传统的递归首字母缩略词,是EINE和ZWEI,代表EINE Is 非 Emacs和ZWEI最初是EINE。后来的人使用了ZWEI的后代,命名为ZMACS。
3实际上,语言标准本身修改的可能性很小 – 虽然人们可能希望清理一小部分瑕疵,但ANSI程序不适合打开现有的小调整标准,并且没有任何可能被清理的疣实际上会导致任何严重的困难。Common Lisp标准化的未来很可能通过事实上的标准进行,就像Perl和Python的“标准化”一样 – 不同的实现者尝试使用应用程序编程接口(API)和库来做未在语言标准中指定的事情,
4钢铁银行Common Lisp
5 CMU Common Lisp
6 SBCL从CMUCL派出,专注于清理内部结构并使其更易于维护。但叉子很和蔼可亲; 错误修复倾向于在两个项目之间传播,并且有人说他们有一天会合并在一起。
7令人尊敬的“你好,世界”甚至早于经典的Kernighan和Ritchie C书,它在其普及中发挥了重要作用。最初的“你好,世界”似乎来自Brian Kernighan的“语言B导论”,它是贝尔实验室计算科学技术报告#8:编程语言B的一部分,于1973年1月出版。(可在线获取) at http://cm.bell-labs.com/cm/cs/who/dmr/bintro.html。)
8这些是一些其他表达式,也打印字符串“你好,世界”:
9好吧,正如您将在讨论返回多个值时所看到的那样,在技术上可以编写无法计算值的表达式,但即使这样的表达式NIL在需要值的上下文中进行求值时也会被视为返回 。
10我将在第4章讨论为什么名称已全部转换为大写。
11您也可以在REPL中将函数输入为两行,因为REPL读取整个表达式而不是行。
12 SLIME快捷方式不是Common Lisp的一部分 – 它们是对SLIME的命令。
13如果由于某种原因加载不能干净利落,您将收到另一个错误并退回到调试器中。如果发生这种情况,最可能的原因是Lisp无法找到该文件,可能是因为它对当前工作目录的想法与文件所在的位置不同。在这种情况下,您可以通过键入退出调试器q,然后使用SLIME快捷方式cd更改Lisp对当前目录的想法 – 键入逗号,然后cd在提示输入命令时,然后输出你好.lisp保存目录的名称。
14 http://www.flownet.com/gat/jpl-lisp.html
http://mip.i3geek.com