├── README.md ├── 第1章 开始 Lisp 之旅 GETTING STARTED WITH LISP.md └── 第2章 创建你的第一个 LISP 程序 Creating Your First Lisp Program.md /README.md: -------------------------------------------------------------------------------- 1 | Land-of-lisp-CN 2 | =============== 3 | 4 | 《Land of Lisp》中文翻译 || Translating [Land of Lisp] to Chinese 5 | 6 | 《Land of Lisp》是一本很不错的 Common Lisp 教程,特别适合于初学者,计划把它的内容翻译为中文。 7 | 8 | 笔者一向认为,学习一种技术,最好能准备两到三种不同的教材,这样就可以从不同的角度去了解这门技术,有助于初学者迅速理解,有时候某本书里某个概念半天想不明白,翻开另外一名作者写的书,从不同的角度描述的这个概念,也许很容易就弄懂了,所以学习 Common Lisp 也不要拘泥于一本书,而是尽量多看看,因此基于这种考虑,笔者选择了这本《Land of Lisp》作为学习 Common Lisp 的补充教材。 9 | 10 | 事实上这本书本身也很有趣,基本上通过各种小游戏的开发把 Common Lisp 的相关概念都讲解了一番。 11 | 12 | 翻译的原因其实很简单,笔者英文水平一般,但是很多时候又不得不去阅读英文技术文档,虽然当时看懂了,可是有时想回头再看看,于是又得重复一遍“大脑翻译”的过程,于是就想,干脆第一遍读懂的时候就直接翻译成中文文档,这样不仅自己回头看方便,也可以分享给其他跟我一样英语不怎么好的技术爱好者,于是就有了这个发愿。因此,译文中必然有一些翻译得不甚准确的地方,希望水平高的读者发现了能提醒笔者修改,谢谢! 13 | 14 | 本文的翻译会在两个地方发布,一个是 `github`,一个是笔者在源中国社区的技术博客 `http://my.oschina.net/freeblues/blog?catalog=516771` 15 | 16 | 《Land of Lisp》 的电子版下载地址很多,貌似出版社就提供了电子版。 17 | 18 | 这里有一个很有趣的关于《Land of Lisp》的动画视频,可以看看: 19 | 20 | http://v.youku.com/v_show/id_XMjg0NDcxMTc2.html 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /第1章 开始 Lisp 之旅 GETTING STARTED WITH LISP.md: -------------------------------------------------------------------------------- 1 | # 第1章 开始 Lisp 之旅 GETTING STARTED WITH LISP 2 | 3 | 翻译者:Freeblues 4 | 5 | github版本:https://github.com/FreeBlues/Land-of-lisp-CN 6 | 7 | 开源中国版本:http://my.oschina.net/freeblues/blog?catalog=516771 8 | 9 | ## 目录 10 | 11 | * Lisp 方言 12 | * 两种 Lisp 的故事 A Tale of Two Lisps 13 | * Lisp 的前景 Up-and-Coming Lisps 14 | * 被用于脚本的 Lisp 方言 Lisp Dialects Used for Scripting 15 | * ANSI Common Lisp 16 | * 开始 CLISP 之旅 17 | * 安装 CLISP Installing CLISP 18 | * 启动 CLISP Starting Up CLISP 19 | * 本章你学到的 What You’ve Learned 20 | 21 | 22 | 本章从介绍 Lisp 的各种方言开始。然后我们会讨论一点 ANSI Common Lisp,也就是我们将会在本书使用的 Lisp 方言。最后你将会从安装和试验 CLISP 开始,这是 ANSI Common Lisp 的一种实现,它将会运行本书中你所创建的所有 Lisp 游戏程序。 23 | 24 | ## Lisp 方言 25 | 26 | 任何遵守 Lisp 核心原则的程序语言都被认为是一种 Lisp 方言。既然这些原则是如此简单,所以毫不奇怪确确实实有一百多种 Lisp 方言被创造出来。事实上,既然这么多崭露头角的 Lisper 创造出他们自己的 Lisp 方言来作为一种练习,遍及整个星球可能会有上千种部分完成的 Lisp 沉睡于硬盘的冗长目录中。然而,绝大多数的 Lisp 社区使用这两种 Lisp 方言:ANSI Common Lisp(常被缩写为 CL)和 Scheme。 27 | 28 | 在本书中,我们将仅仅讨论 ANSI Common Lisp 方言,它在这两种方言中稍微流行一些。尽管如此,你从阅读本书获得的大多数知识也与 Scheme 有关(尽管在两种方言之间关于函数名字的内容趋于不同)。 29 | 30 | ### 两种 Lisp 的故事 A Tale of Two Lisps 31 | 32 | 在 ANSI Common Lisp 和 Scheme 之间存在一些深刻的哲学思想的不同,并且它们引来对不同程序员的人身攻击。一旦你学习到更多 Lisp 语言的知识,你就能决定你更喜欢哪一种方言。这里的选择不存在对或错。 33 | 34 | 为了帮助你做出自己的决定,我为你创建了下面的个人化测试: 35 | 36 | ![C1-1个性测试图](file:///) 37 | 38 | 如果你选择 A,你喜欢具备原始能力的语言。你不在乎你的语言是否有一点丑陋,因为大量出于务实的妥协,只要你仍然可以写出紧凑的代码。ANSI Common Lisp 是你最好的语言!ANSI Common Lisp 追溯它的起源更直接来自原始的 Lisp 方言,建立在数以百万的程序员小时之上,赋予它极其丰富的功能。固然,它有一些巴洛克式的函数名带来无数的历史事故,但是这种 Lisp 确实能够在正确的黑客手中飞翔。 39 | 40 | 如果你选择 B,你喜欢洁净和优雅的语言。你对基本编程问题更感兴趣 ,并且乐于在美丽的草地上消磨时光,深入思考你代码的美丽,偶尔写写关于理论计算问题的研究论文。Scheme 就是为你准备的语言!它在1970年代中期由 Guy L. Steele 和 Gerald Jay Sussman 创造,并且受到一些对理想 Lisp 反省式的影响。Scheme 的代码趋于稍微啰嗦,因为比起创建最短程序的可能性, Schemer 更关注他们代码的数学纯粹性。 41 | 42 | 如果你选择 C,你就是那种希望希望同时得到两者的人: ANSI CL 的威力和 Scheme 的数学美。现在,还没有任何一种 Lisp 方言完全符合你的要求,不过这一点将会在未来发生改变。有一种可能满足你要求的语言(尽管在一本 Lisp 书中做出这个断言是一种亵渎/不敬)是 Haskell。它不被看做是一种 Lisp 方言,不过它的追随者们遵守那些流行于 Lisper 中的范例,比如保持语法统一性,支持本地列表,并且严重依赖高阶函数。更重要的是,它具备极端的数学严格(甚至超过 Scheme)允许它把非常强大的功能隐藏在一个机器洁净的界面下。它本质上就是一只披着羊皮的狼。跟 Lisp 一样,Haskell 是这样一种语言,任何程序员都会在未来对它的研究中获益。 43 | 44 | ### Lisp 的前景 Up-and-Coming Lisps 45 | 46 | 就像刚才提到的,实际上不存在一种 Lisp 方言,它既拥有 ANSI Common Lisp 的强大和灵活,又有 Scheme 的优雅。然而,一些新崛起的挑战者可能赢得这两个世界最好的奖杯在不远的将来。 47 | 48 | 一种前景很好的新的 Lisp 是 Clojure,由 Rich Hickey 开发的一种方言。Clojure 构建于 Java 平台之上,使得它获得大量成熟 Java 库的立即可用的影响。Clojure 还包括一些聪明而且深思熟虑的特性用于简化多线程编程,使得它成为一种有用的工具用于为看起来随处可见的多核 CPU 编程。 49 | 50 | 另一个有趣的挑战者是 Arc。它是一种主要由著名 Lisper Paul Graham 开发的真正的 Lisp 语言。Arc现在仍然处于开发的早期阶段,关于它在其他 Lisp 之上做了多大改善,还存在很多争议。它的开发进度目前还在以被冻结一样的缓慢速度进行着。在任何人宣称 Arc 将成为一个有意义的挑战者之前还需要一段时间。 51 | 52 | 我们将在结语中对 Arc 和 Clojure 略作品鉴。 53 | 54 | ### 被用于脚本的 Lisp 方言 Lisp Dialects Used for Scripting 55 | 56 | 一些 Lisp 方言被用于脚本编程,如下: 57 | 58 | Emacs Lisp 被流行的() Emacs 文本编辑器用作内部的脚本。 59 | 60 | Guile Scheme 在一些开源应用中被作为一种脚本语言来使用。 61 | 62 | Script-Fu Scheme 被用于 GIMP 图形编辑器。 63 | 64 | 这些方言是 Lisp 主要分支的更旧版本的分支,并且很少被用于建立独立的应用程序。然而,他们仍然是完美体面的 Lisp 方言。 65 | 66 | ### ANSI Common Lisp 67 | 68 | 1981年,为了应付这种语言那令人眩晕的方言数量,各个不同的 Lisp 社区共同起草了一份被称为 Common Lisp 的新方言的规格。在1986年,经过一些调整之后,这种语言变成了 ANSI Common Lisp 标准。很多老版本 Lisp 的开发者修改了他们的解释器和编译器来使之符合新标准,这个新标准就此成为最流行的 Lisp 版本并且保持至今。 69 | 70 | 注意 71 | 72 | 贯穿本书,术语 Common Lisp 指的是按照 ANSI 标准定义的 Common Lisp 版本。 73 | 74 | Common Lisp 的一个关键设计目标是创建一种多范式语言,意思是它要包括对许多种不同编程模式的支持。你可能听说过面向对象编程,可以非常好地在 Common Lisp 中做到。其他你可能没有听说过的编程模式,包括函数式编程,通用编程以及特定领域语言编程(DSL)。这些在 Common Lisp 中都可以很好地支持。你将会学习这里的每一种编程模式,一个接着一个,随着我们这本书的推进。 75 | 76 | 77 | ## 开始 CLISP 之旅 78 | 79 | 很多伟大的 Lisp 编译器都可以用,不过有一种尤其容易上手:CLISP,一种开源的 Common Lisp。CLISP的安装非常简单,而且可以运行在任何操作系统上。 80 | 81 | 其他流行的 Lisp 包括 Steel Bank Common Lisp (SBCL),一种快速的 Lisp,被认为比 CLISP 更重型一点;Allegro Common Lisp,由 Franz,Inc 开发的强大的商业 Lisp;Clozure CL;以及 CMUCL。Mac 用户可能认为 LispWorks 或 Clozure CL 更容易运行在他们的机器上。然而,对于我们的目标来说,CLISP 是最好的选择。 82 | 83 | 注意 84 | 85 | 从第12章开始,我们将会使用一些 CLISP特定的命令,这些命令被认为是非标准的。不过,直到那一章,Common Lisp 的任一种实现都可以运行本书中的例子。 86 | 87 | ### 安装 CLISP Installing CLISP 88 | 89 | 你可以从 http://clisp.cons.org/ 下载一个 CLISP 安装程序,它可以在 Windows PC,Mac,以及不同变体的 Linux 上运行。在一台 Windows PC 上,你只要简单运行一个安装程序即可。在一台 Mac 上,有一些额外的步骤,这些细节都在网站上有详细描述。 90 | 91 | 在一台基于 Debian 的 Linux 机器上,你应该可以发现 CLISP 以及存在你的标准源代码中了。只需要在命令行输入 92 | 93 | apt-get install clisp 94 | 95 | 然后,CLISP 就会自动安装好。 96 | 97 | 对于其他的 Linux 发行版本(如 Fedora,SUSE 等等),你可以使用列在 CLISP 网站的 “Linux packages”下的标准包。而且,有经验的 Linux 用户可以通过源代码来编译 CLISP。 98 | 99 | ### 启动 CLISP Starting Up CLISP 100 | 101 | 在你的命令行输入: 102 | 103 | clisp 104 | 105 | 就可以运行 CLISP。如果一切正常,你将会看到如下的提示画面: 106 | 107 | $ clisp 108 | i i i i i i i ooooo o ooooooo ooooo ooooo 109 | I I I I I I I 8 8 8 8 8 o 8 8 110 | I \ `+' / I 8 8 8 8 8 8 111 | \ `-+-' / 8 8 8 ooooo 8oooo 112 | `-__|__-' 8 8 8 8 8 113 | | 8 o 8 8 o 8 8 114 | ------+------ ooooo 8oooooo ooo8ooo ooooo 8 115 | 116 | Welcome to GNU CLISP 2.49 (2010-07-07) 117 | 118 | Copyright (c) Bruno Haible, Michael Stoll 1992, 1993 119 | Copyright (c) Bruno Haible, Marcus Daniels 1994-1997 120 | Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998 121 | Copyright (c) Bruno Haible, Sam Steingold 1999-2000 122 | Copyright (c) Sam Steingold, Bruno Haible 2001-2010 123 | 124 | Type :h and hit Enter for context help. 125 | 126 | [1]> 127 | 128 | 和所有的 Common Lisp 环境一样,CLISP 在你启动它之后会自动把你带到它的一个 读取-求值-打印-循环(REPL)中(read-eval-print loop)。这意味着你能立即开始输入 Lisp 代码。 129 | 130 | 试着输入: 131 | 132 | (+ 3 (* 2 4)) 133 | 134 | 你将会看到结果被打印在这个表达式下面: 135 | 136 | [1]> (+ 3 (* 2 4)) 137 | 11 138 | 139 | 这个实验演示了 REPL 是如何工作的。你输入一个表达式,然后 Lisp 将会立即求值并且返回结果值。当你想要关闭 CLISP 时,输入(quit)即可(译者注:需要连括号一起输入)。 140 | 141 | 不论如何你已经让 CLISP 运行在你的计算机上了,做好准备编写一个 Lisp 游戏! 142 | 143 | ## 本章你学到的 What You’ve Learned 144 | 145 | 在本章中,我们讨论了 Lisp 的不同方言并且安装了 CLISP。通过这种方法你学到如下内容: 146 | 147 | * 有两种主要的 Lisp 方言:Common Lisp 和 Scheme。它们都提供了很多功能,不过在本书中我们将会聚焦于 Common Lisp。 148 | 149 | * Common Lisp 是一种多范式语言,意味着它支持许多不同的编程模式。 150 | 151 | * CLISP 是这样一种 Common Lisp 实现,它非常容易设置,使得对于一个 Lisp 初学者来说,它是一种出色的选择。 152 | 153 | * 你可以从 CLISP 的 REPL 中直接输入 Lisp 命令。 154 | 155 | ![C1-2漫画图](file:///) 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /第2章 创建你的第一个 LISP 程序 Creating Your First Lisp Program.md: -------------------------------------------------------------------------------- 1 | # 第2章 创建你的第一个 LISP 程序 Creating Your First Lisp Program 2 | 3 | 翻译者:FreeBlues 4 | 5 | github版本:https://github.com/FreeBlues/Land-of-lisp-CN 6 | 7 | 开源中国版本:http://my.oschina.net/freeblues/blog?catalog=516771 8 | 9 | ## 目录 10 | 11 | *** 12 | 13 | - [猜数字游戏 The Guess-My-Number Game](#猜数字游戏) 14 | - [定义全局变量 Defining Global Variables in Lisp](#定义全局变量) 15 | - [定义“小”和“大”变量 Defining the small and big Variables](#定义“小”和“大”变量) 16 | - [全局变量定义函数的替代 An Alternative Global Variable Definition Function](#全局变量定义函数的替代) 17 | - [Lisp的基本规矩 Basic Lisp Etiquette](#Lisp的基本规矩) 18 | - [定义全局函数 Defining Global Functions in Lisp](#) 19 | - [定义猜数函数 Defining the guess-my-number Function](#定义猜数函数) 20 | - [定义更小更大函数 Defining the smaller and bigger Functions](#定义更小更大函数) 21 | - [定义重新开始函数 Defining the start-over Function](#定义重新开始函数) 22 | - [定义局部变量 Defining Local Variables in Lisp](#定义局部变量) 23 | - [定义局部函数 Defining Local Variables in Lisp](#定义局部函数) 24 | - [你学到什么 What You’ve Learned](#你学到什么) 25 | 26 | 既然我们已经讨论过 `Lisp` 的一些哲学并且拥有了一个正常运行的 `CLISP` 环境,我们就准备以一个简单游戏的形式来写一些实际的 Lisp 代码。 27 | 28 | ## 猜数字游戏 29 | 30 | 我们将要编写的第一个游戏是能想到的最简单的游戏。 它是一个经典的猜数游戏。 31 | 32 | 在这个游戏中,你选择一个从1到100的数字,接着计算机要把它猜出来。 33 | 34 | 接下来展示了当你选取数字23时游戏玩起来是什么样子。计算机以50开始猜,并且随着每次不停的猜测,你要输入 (smaller) 或 (bigger) 直到计算机猜中你的数字。 35 | 36 | >(guess-my-number) 37 | 50 38 | >(smaller) 39 | 25 40 | >(smaller) 41 | 12 42 | >(bigger) 43 | 18 44 | >(bigger) 45 | 21 46 | >(bigger) 47 | 23 48 | 49 | 为了创建这个游戏,我们需要编写3个函数:guess-my-number,bigger 和 smaller。玩家简单地从 REPL 调用这3个函数就可以了。正如你在前面章节所看到的,当你启动 CLISP (或者其他 Lisp),REPL 将会呈现在你面前,通过它你输入的命令可以被读取(*read*),然后被求值(*evaluated*),最后被打印出来(*printed*)。在这个例子里,我们要运行命令 guess-my-number,bigger 和 smaller。 50 | 51 | 在 Lisp 中调用函数,你要把这个函数和打算传给这个函数的所有参数一起用括号括起来。既然一部分函数不需要任何参数,我们只要用括号把它们的名字括起来就可以了。 52 | 53 | 让我们考虑一下这个简单游戏背后的策略。简单思考一下,我们通过以下步骤来逐步实现: 54 | 55 | 1.确定玩家数字的上限和下限(大和小)。因为范围在1到100之间,最小的数字应该是1,最大的数字应该是100。 56 | 2.在这两个数字之间猜一个数。 57 | 3.如果玩家说真实数字更小一些,降低上限(最大数字)。 58 | 4.如果玩家说真实数字更大一些,增加下限(最小数字)。 59 | 60 | ![C2-1图](file:///) 61 | 62 | 通过上述简单步骤,每次猜测都把可能的数字范围缩小一半,计算机能很快找出玩家的数字。 63 | 64 | 这种搜索算法被称为二分法(*binary search*),正如你所知,像这样的二分法一直被用于计算机编程中。你能沿用相同的步骤,例如,高效地找到一个特定的数字,从被给定的数值的排序表中。在这个例子里,你可以简单地追踪表里的最小行和最大行,并且很快找到正确的行,以一种类似的方式。 65 | 66 | ## 定义全局变量 67 | 68 | 当玩家调用那些构成我们游戏的函数时,程序需要追踪下限和上限。为了做到这一点,我们需要创建两个被称为 \*small\* 和 \*big\* 的全局变量。 69 | 70 | ### 定义“小”和“大”变量 71 | 72 | 在 Lisp 中一个被定义为全局的变量被称为一个顶层定义(*top-level definition*)。我们可以使用函数 defparameter 来创建新的顶层定义: 73 | 74 | >(defparameter *small* 1) 75 | *small* 76 | >(defparameter *big* 1) 77 | *big* 78 | 79 | 函数名 `defparameter` 会带来一点困惑,因为它实际上没有对参数(`parameter`)做任何操作。它所做的就是让你定义一个全局变量(*global variable*)。 80 | 81 | 我们发送给 defparameter 第一个参数是心变量的名字。环绕名字 \*small\* 和 \*big\* 前后的星号(*)--被昵称为耳套(earmuffs)--完全是随意和可选的。Lisp 把星号当做变量名的一部分并且忽略掉它们。Lisper 喜欢以这种方式作为一个约定俗成的惯例为他们的全局变量标上星号,以便它们可以更容易和局部变量区分开来,在本章的后面将会讨论这一点。 82 | 83 | 注意 84 | 85 | 尽管耳套在严格技术意义上来说是可选的,我还是建议使用它们。我无法保证你的安全,如果你把代码贴到一个 Common Lisp 新闻组并且你的全局变量没有带耳套。 86 | 87 | ### 全局变量定义函数的替代 88 | 89 | 当你使用 defparameter 设置一个全局变量的值时,任何之前存储在这个变量中的值都会被重写覆盖掉: 90 | 91 | >(defparameter *foo* 5) 92 | FOO 93 | >*foo* 94 | 5 95 | >(defparameter *foo* 6) 96 | FOO 97 | >*foo* 98 | 6 99 | 100 | 正如你所见,当我们重新定义了变量 \*foo\* 之后,它的值变了。 101 | 102 | 另一个可以用来声明全局变量的命令被称为 defvar ,它不会覆盖掉一个全局变量之前的值: 103 | 104 | > (defvar *foo* 5) FOO 105 | > *foo* 106 | 5 107 | > (defvar *foo* 6) FOO 108 | > *foo* 109 | 5 110 | 111 | 注意 112 | 113 | `当你在其他地方阅读关于 Lisp 的知识时,你可能也会看到程序员们在 Common Lisp 中使用术语动态变量(dynamic variable)或特殊变量(special variable)来指一个全局变量。这是因为 Common Lisp 中的全局变量有一些特殊的能力,我们将会在后面的章节讨论这些。` 114 | 115 | ## Lisp的基本规矩 116 | 117 | 跟其他语言比起来,Lisp 中命令被调用的方式和代码被格式化的方式有些奇怪。首先,我们需要用括号把命令(以及命令的参数)括起来,就像 defparameter 函数一样: 118 | 119 | >(defparameter *small* 1) 120 | *small* 121 | 122 | 缺少括号的话,命令不会被调用。 123 | 124 | 此外,空格和换行被完全忽略,当 Lisp 读入你的代码时。这意味着你能用任何疯狂的方式来调用这个命令,而结果不会变: 125 | 126 | > ( defparameter 127 | *small* 1) 128 | *SMALL* 129 | 130 | 因为 Lisp 代码能以这种灵活的方式格式化,Lisper 对于格式化命令有很多约定俗成的惯例,包括什么时候使用多行和缩进。在本书的代码实例上,我们将会大致遵循一些常见的缩排惯例。不过,相对于讨论源代码缩排规则我们更感兴趣的是编写一些游戏,因此本书中我们将不会在代码布局上花费过多时间。 131 | 132 | ## 定义全局函数 133 | 134 | 我们的猜数游戏通过计算机对玩家请求的响应来开始游戏,然后请求更小或更大的猜测。为了实现这些,我们需要定义3个全局函数:guess-my-number,bigger 和 smaller。我们还要定义一个名为 start-over 的函数,用来重新开始游戏以一个不同的数字。在 Common Lisp 中,用 `defun` 来定义函数,如下所示: 135 | 136 | (defun function_name (参数) 137 | ...) 138 | 139 | 首先,我们为一个函数指明名字和参数。然后我们接着写组成函数处理逻辑的代码。 140 | 141 | ### 定义猜数函数 142 | 143 | 我们定义的第一个函数是 guess-my-number。这个函数使用变量 \*small\* 和 \*big\* 的值来生成一个针对玩家数字的猜测。定义如下所示: 144 | 145 | > (defun guess-my-number () 146 | (ash (+ *small* *big*) -1)) 147 | GUESS-MY-NUMBER 148 | 149 | 在函数名字 guess-my-number 之后的空括号 () 指明这个函数不需要参数。 150 | 151 | 尽管在把片段代码输入到 REPL 时不需要担心缩排和断行,你必须确保把括号的位置放置正确。如果你忘掉一个后括号或者把一个括号放到了错误的位置上,你很可能会得到一个错误。 152 | 153 | 当我们任何时候像这样在 REPL 里运行一段代码时,输入表达式的结果值将会被打印出来。Common Lisp 中的每一个命令都会产生一个返回值。例如 `defun` 命令简单地返回新建函数的函数名。这就是为什么我们看到在我们调用 `defun` 之后在 REPL 中函数名被鹦鹉学舌般返回给我们。 154 | 155 | 这个函数做了什么?正如之前讨论过的,这个游戏中计算机最好的猜测将是一个介于两个限制之间的数字。为了完成这一点,我们选择两个限制的平均值。然而,如果平均值以一个分数结尾的话,我们想要使用近似(near-average)数,因为我们猜测的是完整的数字。 156 | 157 | 我们在函数 guess-my-number 中实现这些功能通过以下处理:首先把上限值和下限值加在一起,,然后使用算数移位函数 `ash` ,来使上限值、下限值之和减半并且截短结果。代码 `(+ *small* *big*)` 把这两个变量加起来。因为加法用另一个函数调用, <1> ,加的结果被接着传递给函数 `ash`。 158 | 159 | 包围函数 `ash` 和函数 `(+)` 的括号在 Lisp 中是必须要有的。这些括号告诉 Lisp “我想让你马上调用这个函数”。 160 | 161 | 内置的(build-in) Lisp 函数 `ash` 以二进制的方式看待一个数字,然后把它所有的二进制位(bits)同时移向左边或右边,丢掉在这个过程中失去的任何位(译者注:)。例如,十进制数字 `11` 用二进制表达就是 0000`1011`。我们可以向左移动这个数字里所有的位,通过 `ash` 把 `1` 作为第二个参数: 162 | 163 | >(ash 11 1) 164 | 22 165 | 166 | 这样就产生了 `22`,二进制是 000`10110`。我们也可以把所有位向右移动(去掉了最后的一位 `1`)通过用 `-1` 作为第二个参数: 167 | 168 | >(ash 11 -1) 169 | 5 170 | 171 | 这样会产生5,二进制是 00000`101`。 172 | 173 | 通过在 `guess-my-number` 中使用函数 `ash`,我们可以连续减半可能数字的搜索空间来快速缩小最终正确数字的范围。正如之前提到的,这种减半处理被称为`二分搜索`,一种在计算机编程中很有用的技术。函数 `ash` 经常被用于 Lisp 中这些`二分搜索`。 174 | 175 | 让我们看看当我们的新函数被调用时将会发生什么: 176 | 177 | >(guess-my-number) 178 | 50 179 | 180 | 因为这是第一次猜测,我们看到调用这个函数的输出告诉我们一切都按计划进行:程序选择了数字 `50`,正好位于 `1` 和 `100` 的中间。 181 | 182 | 在用 Lisp 编程时,你将会写很多函数,它们不会明确打印值到屏幕上。作为替代,它们将会简单地把函数体的计算值返回。例如,我们说我们想要一个函数仅仅返回数字 5 ,我们可以这样写: 183 | 184 | > (defun return-five () 185 | (+ 2 3)) 186 | 187 | 因为函数体里计算的值被求值为 5,调用 `(return-five)` 只会返回 5。 188 | 189 | 这就是 guess-my-number 的设计思路。我们看到这个被计算后的结果出现在屏幕上(数字 50)不是因为函数使这个数字显示,而是因为这是 REPL 的一个特性。 190 | 191 | 注意 192 | 193 | `如果你之前使用过其他编程语言,你可能记得为了让一个值被返回不得不写一些类似 return… 的东西。在 Lisp 中,这是不必要的。函数体中被计算的最终值会被自动返回` 194 | 195 | ### 定义更小更大函数 196 | 197 | 现在要写我们的 `smaller` 和 `bigger` 函数了。像 `guess-my-number` 一样,这些都是用 `defun` 定义的全局函数: 198 | 199 | > (defun smaller () 200 | (setf *big* (1- (guess-my-number))) 201 | (guess-my-number)) 202 | SMALLER 203 | > (defun bigger () 204 | (setf *small* (1+ (guess-my-number))) 205 | (guess-my-number)) 206 | BIGGER 207 | 208 | 首先,我们使用 `defun` 来开始一个新全局函数 `smaller` 的定义。因为这个函数不带任何参数,所以函数名后面的括号是空的 <1>。 209 | 210 | 接着,我们使用 `setf` 函数来改变我们全局变量 `*big*` 的值 <2>。因为我们知道那个数字必须要比上次猜的值更小一些,最大的它现在是比猜测值要小的那个。代码 `(1- (guess-my-number))` 这么计算:首先调用函数 `guess-my-number` 来获得最近的猜测值,然后对这个猜测值使用函数 `1-`,会从猜测值里减去 `1`。 211 | 212 | 最后,我们想要函数 `smaller` 给我们显示一个新的猜测值。我们通过把函数 `guess-my-number` 放在函数体的最后一行来实现 <3>。这一次,`guess-my-number` 将会使用更新过的 `*big*` 值,用这个值来计算下一个猜测值。我们的函数的最终的值将会自动返回,使得我们新的猜测值(由 `guess-my-number` 产生)通过函数 `smaller` 产生。 213 | 214 | 函数 `bigger` 以相同的方式工作,除了它是把 `*small*` 的值增加之外。终究,如果你调用函数 `bigger`,你就是在说你的数字要比上一次猜测值更大,因此最小的它现在要比(就是变量 `small` 所对应的值)前一次猜测值更大。函数 `1+` 简单地在由 `guess-my-number` 返回的猜测值上加 `1` <4>。 215 | 216 | 可以在这里看到当程序猜了 `56` 时我们函数的运行情况: 217 | 218 | > (bigger) 219 | 75 220 | > (smaller) 221 | 62 222 | > (smaller) 223 | 56 224 | 225 | ### 定义重新开始函数 226 | 227 | 为了完成我们的游戏,我们将会增加函数 `start-over` 来重新设置我们的全局变量: 228 | 229 | (defun start-over () 230 | (defparameter *small* 1) 231 | (defparameter *big* 100) 232 | (guess-my-number)) 233 | 234 | 正如你所见,函数 `start-over` 重置了变量 `*small*` 和 `*big*`,接着再次调用函数 `guess-my-number` 来返回一个重新开始的游戏。不论何时只要你想启动一个使用不同数字的崭新游戏时,你都可以调用这个函数来重置游戏。 235 | 236 | ## 定义局部变量 237 | 238 | 为了我们简单的游戏,我们已经定义了全局变量和全局函数。然而,大多数情况下你可能想把定义限制在一个单独的函数中或者是一块代码内。这些就是被称为局部变量和局部函数。 239 | 240 | 定义一个局部变量。要使用命令 `let`。一个 `let` 命令有着如下结构: 241 | 242 | (let (variable declarations) 243 | ...body...) 244 | 245 | 在 `let` 命令中的第一部分是一个变量声明的列表。在这里我们可以声明一个或多个局部变量 <1>。接着,在命令体里(并且仅仅在这个体内),我们能使用这些变量 <2>。这里是关于 `let` 命令的一个例子: 246 | 247 | > (let ((a 5) 248 | (b 6)) 249 | (+ab)) 250 | 11 251 | 252 | 在这个例子中,我们分别为变量 `a` <1> 和 `b` <2> 声明了值 `5` 和 `6`。这些就是我们的变量声明。然后,在命令 `let` 的体内,我们把它们加在一起 <3>,显示出结果值 `11`。 253 | 254 | 在使用一个 `let` 表达式时,你必须用括号把被声明的变量全部括到一起。另外,你必须把每一对变量名字和初始化变量值用另一对括号括起来。 255 | 256 | 注意 257 | 258 | 尽管缩排和断行是完全随意的,因为在一个 let 表达式里的变量名和它们的值形成了一种简单的表格,提倡的经验是把被声明的变量垂直对齐。这就是为什么在上一个例子中 b 被直接置于 a 的下方。 259 | 260 | ## 定义局部函数 261 | 262 | 我们用 `flet` 命令来定义局部函数。命令 `flet` 有着如下结构: 263 | 264 | (flet ((function_name (arguments) 265 | ...function body...)) 266 | ...body...) 267 | 268 | 在 `flet` 的顶部,我们声明了一个函数(在起始两行)。这个函数接着在这个主体内将对我们可用 <3>。一个函数声明包括一个函数名字,函数的参数 <1>,以及函数主体 <2>,在那里我们将放置函数的代码。 269 | 270 | 这里是一个例子: 271 | 272 | > (flet ((f (n) 273 | (+ n 10))) 274 | (f5)) 275 | 15 276 | 277 | 在这个例子中,我们定义了一个独立的函数 `f` ,它带着一个单独的参数,`n` <1>。函数 `f` 把 `10` 加到变量 `n` 上 <2>,被传给它的。=== 接下来我们使用数字 `5` 作为参数来调用这个函数,值 `15` 会被返回 <3>。 278 | 279 | 跟 `let` 一样,你能在 `flet` 的范围内(译者注:也就是在 `flet` 的顶部)定义一个或多个函数。 280 | 281 | 一个单独的 `flet` 命令能被用来一次定义多个本地函数。简单地在命令的第一部分增加多个函数声明就可以了: 282 | 283 | > (flet ((f (n) 284 | (+ n 10)) 285 | (g (n) 286 | (- n 3))) 287 | (g (f 5))) 288 | 12 289 | 290 | 在这里,我们声明了两个函数:一个名为 `f` <1>,一个名为 `g` <2>。在 `flet` 的主体部分,我们可以立即使用这两个函数。在这个例子里,主体先使用参数 `5` 调用 `f` 得到 `15`,接着调用 `g` 来减去 `3`,最终得到的结果是 `12`。 291 | 292 | 为了使得函数名在被定义的函数中也可用(译者注:此处是指同时定义函数可以相互调用),我们可以使用命令 `labels` 。它的基本结构跟命令 `flet` 相同。这里是一个例子: 293 | 294 | > (labels ((a (n) 295 | (+ n 5)) 296 | (b (n) 297 | (+ (a n) 6))) 298 | (b 10)) 299 | 21 300 | 301 | 在这个例子里,局部函数 `a` 把 `5` 加到一个数字上 <1>。接着,函数 `b` 被声明 <2>。函数 `b` 调用了函数 `a`,然后在结果上加 `6` <3>。最终,函数 `b` 使用参数值 `10` 被调用 <4>。因为 `10` 加 `6` 加 `5` 等于 `21`,数字 `21` 成为整个表达式的最终值。当我们想要用函数 `b` 调用函数 `a` 时 <3>,就需要我们选择 `labels` 而不是 `flet`。如果我们用了 `flet`,函数 `b` 是不会"知道"函数 `a`的。 302 | 303 | 命令 `labels` 允许我们使用一个局部函数调用另一个,同时它也允许我们用一个函数调用它自己。这种做法在 Lisp 代码中很常见,被称为递归(你将会在未来的章节中看到很多关于递归的例子)。 304 | 305 | ## 你学到什么 306 | 307 | 本章中,我们讨论了用于定义变量和函数的基本 Common Lisp 命令。一路走来,你学到了如下内容: 308 | 309 | * 定义一个全局变量,使用 `defparameter` 命令。 310 | 311 | * 定义一个全局函数,使用 `defun` 命令。 312 | 313 | * 分别使用 `let` 和 `flet` 命令来定义局部变量和局部函数。 314 | 315 | * 函数 `labels` 跟 `flet` 很相似,不过它允许函数自我调用。 316 | 317 | * 调用自己的函数被称为递归函数。 318 | 319 | --------------------------------------------------------------------------------