├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── chap01 │ ├── but-i-learned.html │ ├── introduction.html │ ├── where-it-began.html │ ├── who-is-this-book-for.html │ └── why-lisp.html ├── chap02 │ ├── choosing-a-lisp.html │ ├── experimenting.html │ ├── free-your-mind.html │ ├── getting-up-and-running.html │ ├── hello-world.html │ ├── saving-your-work.html │ └── tour-of-the-repl.html ├── chap03 │ ├── a-simple-database.html │ ├── cds-and-records.html │ ├── filling-cds.html │ ├── improving-user-interaction.html │ ├── looking-at-database.html │ ├── querying-the-database.html │ ├── removing-duplication.html │ ├── saving-and-loading-database.html │ ├── updating-existing-records.html │ └── wrapping-up.html ├── chap04 │ ├── breaking-open-the-black.html │ ├── formatting-lisp-code.html │ ├── function-calls.html │ ├── macros.html │ ├── s-expressions-as-lisp-forms.html │ ├── s-expressions.html │ ├── special-operators.html │ ├── syntax-and-semantics.html │ ├── true-false-and-equality.html │ └── whats-with-all.html ├── chap05 │ ├── anonymous-functions.html │ ├── defining-new-functions.html │ ├── function-parameter-lists.html │ ├── function-return-values.html │ ├── functions-as-data.html │ ├── functions.html │ ├── keyword-parameters.html │ ├── mixing-different-parameters.html │ ├── optional-parameters.html │ └── rest-parameters.html ├── chap06 │ ├── assignment.html │ ├── constants.html │ ├── dynamic-variables.html │ ├── generalized-assignment.html │ ├── lexical-variables-and-closures.html │ ├── other-ways-to-modify-places.html │ ├── variable-basics.html │ └── variables.html ├── chap07 │ ├── and-or-and-not.html │ ├── cond.html │ ├── do.html │ ├── dolist-and-dotimes.html │ ├── looping.html │ ├── macros-standard-control-constructs.html │ ├── the-mighty-loop.html │ └── when-and-unless.html ├── chap08 │ ├── a-sample-macro.html │ ├── beyond-simple-macros.html │ ├── defmacro.html │ ├── generating-the-expansion.html │ ├── macro-expansion-time.html │ ├── macro-parameters.html │ ├── macro-writing-macros.html │ ├── macros-defining-your-own.html │ ├── once-only.html │ ├── plugging-the-leaks.html │ └── the-story-of-mac.html ├── chap09 │ ├── abstraction-emerges.html │ ├── better-result-reporting.html │ ├── building-a-unit-test-framework.html │ ├── fixing-the-return-value.html │ ├── hierarchy-of-tests.html │ ├── refactoring.html │ ├── two-first-tries.html │ └── wrapping-up.html ├── chap10 │ ├── basic-math.html │ ├── character-comparisons.html │ ├── characters.html │ ├── higher-math.html │ ├── numbers-characters-strings.html │ ├── numbers.html │ ├── numeric-comparisons.html │ ├── numeric-literals.html │ ├── string-comparisons.html │ └── strings.html ├── chap11 │ ├── collections.html │ ├── hash-table-iteration.html │ ├── hash-tables.html │ ├── higher-order-function-variants.html │ ├── sequence-iterating-functions.html │ ├── sequence-mapping-functions.html │ ├── sequence-predicates.html │ ├── sorting-and-merging.html │ ├── subsequence-manipulations.html │ ├── subtypes-of-vector.html │ ├── vector-as-sequences.html │ ├── vectors.html │ └── whole-sequence-manipulations.html ├── chap12 │ ├── after-append.png │ ├── combining-recycling-with-shared-structure.html │ ├── destructive-operations.html │ ├── functional-programming-and-lists.html │ ├── list-1-2-3.png │ ├── list-manipulation-functions.html │ ├── list-processing.html │ ├── mapping.html │ ├── mixed-list.png │ ├── one-cons-cell.png │ ├── other-structures.html │ └── there-is-no-list.html ├── chap13 │ ├── alist-abc-123.png │ ├── beyond-lists.html │ ├── copy-list-list-or-tree.png │ ├── copy-tree-list-or-tree.png │ ├── destructuring-bind.html │ ├── list-or-tree.png │ ├── lookup-tables.html │ ├── plist-abc-123.png │ ├── sets.html │ └── trees.html ├── chap14 │ ├── bulk-reads.html │ ├── closing-files.html │ ├── constructing-new-pathnames.html │ ├── file-output.html │ ├── filenames.html │ ├── files-and-file-io.html │ ├── how-pathnames-represent-filenames.html │ ├── how-we-got-here.html │ ├── interacting-with-the-file-system.html │ ├── other-kinds-of-io.html │ ├── reading-binary-data.html │ ├── reading-file-data.html │ └── two-representations.html ├── chap15 │ ├── a-portable-pathname-library.html │ ├── listing-a-directory.html │ ├── packing-the-library.html │ ├── read-time-conditionalization.html │ ├── testing-a-files-existence.html │ ├── the-api.html │ └── walking-a-directory-tree.html ├── chap16 │ ├── account-hierarchy.png │ ├── defgeneric.html │ ├── defmethod.html │ ├── generic-functions-and-classes.html │ ├── generic-functions-and-methods.html │ ├── method-combination.html │ └── object-reorientation.html ├── commonltr.css ├── commonrtl.css └── index.html └── docsrc ├── .gitignore ├── book.ditamap ├── chap01 ├── but-i-learned.md ├── chap01.ditamap ├── introduction.md ├── where-it-began.md ├── who-is-this-book-for.md └── why-lisp.md ├── chap02 ├── chap02.ditamap ├── choosing-a-lisp.md ├── experimenting.md ├── free-your-mind.md ├── getting-up-and-running.md ├── hello-world.md ├── saving-your-work.md └── tour-of-the-repl.md ├── chap03 ├── a-simple-database.md ├── cds-and-records.md ├── chap03.ditamap ├── filling-cds.md ├── improving-user-interaction.md ├── looking-at-database.md ├── querying-the-database.md ├── removing-duplication.dita ├── saving-and-loading-database.md ├── updating-existing-records.md └── wrapping-up.md ├── chap04 ├── breaking-open-the-black.md ├── chap04.ditamap ├── formatting-lisp-code.md ├── function-calls.md ├── macros.md ├── s-expressions-as-lisp-forms.md ├── s-expressions.md ├── special-operators.md ├── syntax-and-semantics.md ├── true-false-and-equality.md └── whats-with-all.md ├── chap05 ├── anonymous-functions.md ├── chap05.ditamap ├── defining-new-functions.md ├── function-parameter-lists.md ├── function-return-values.md ├── functions-as-data.md ├── functions.md ├── keyword-parameters.md ├── mixing-different-parameters.md ├── optional-parameters.md └── rest-parameters.md ├── chap06 ├── assignment.md ├── chap06.ditamap ├── constants.md ├── dynamic-variables.md ├── generalized-assignment.md ├── lexical-variables-and-closures.md ├── other-ways-to-modify-places.md ├── variable-basics.dita └── variables.md ├── chap07 ├── and-or-and-not.md ├── chap07.ditamap ├── cond.md ├── do.md ├── dolist-and-dotimes.md ├── looping.md ├── macros-standard-control-constructs.md ├── the-mighty-loop.md └── when-and-unless.md ├── chap08 ├── a-sample-macro.md ├── beyond-simple-macros.md ├── chap08.ditamap ├── defmacro.md ├── generating-the-expansion.dita ├── macro-expansion-time.md ├── macro-parameters.md ├── macro-writing-macros.md ├── macros-defining-your-own.md ├── once-only.md ├── plugging-the-leaks.md └── the-story-of-mac.md ├── chap09 ├── abstraction-emerges.md ├── better-result-reporting.md ├── building-a-unit-test-framework.md ├── chap09.ditamap ├── fixing-the-return-value.md ├── hierarchy-of-tests.md ├── refactoring.md ├── two-first-tries.md └── wrapping-up.md ├── chap10 ├── basic-math.md ├── chap10.ditamap ├── character-comparisons.md ├── characters.md ├── higher-math.md ├── numbers-characters-strings.md ├── numbers.md ├── numeric-comparisons.md ├── numeric-literals.md ├── string-comparisons.md └── strings.md ├── chap11 ├── chap11.ditamap ├── collections.md ├── hash-table-iteration.md ├── hash-tables.md ├── higher-order-function-variants.md ├── sequence-iterating-functions.md ├── sequence-mapping-functions.md ├── sequence-predicates.md ├── sorting-and-merging.md ├── subsequence-manipulations.md ├── subtypes-of-vector.md ├── vector-as-sequences.md ├── vectors.md └── whole-sequence-manipulations.md ├── chap12 ├── after-append.png ├── chap12.ditamap ├── combining-recycling-with-shared-structure.md ├── destructive-operations.md ├── functional-programming-and-lists.md ├── list-1-2-3.png ├── list-manipulation-functions.md ├── list-processing.md ├── mapping.md ├── mixed-list.png ├── one-cons-cell.png ├── other-structures.md └── there-is-no-list.md ├── chap13 ├── alist-abc-123.png ├── beyond-lists.md ├── chap13.ditamap ├── copy-list-list-or-tree.png ├── copy-tree-list-or-tree.png ├── destructuring-bind.md ├── list-or-tree.png ├── lookup-tables.md ├── plist-abc-123.png ├── sets.md └── trees.md ├── chap14 ├── bulk-reads.md ├── chap14.ditamap ├── closing-files.md ├── constructing-new-pathnames.md ├── file-output.md ├── filenames.md ├── files-and-file-io.md ├── how-pathnames-represent-filenames.md ├── how-we-got-here.md ├── interacting-with-the-file-system.md ├── other-kinds-of-io.md ├── reading-binary-data.md ├── reading-file-data.md └── two-representations.md ├── chap15 ├── a-portable-pathname-library.md ├── chap15.ditamap ├── listing-a-directory.md ├── packing-the-library.md ├── read-time-conditionalization.md ├── testing-a-files-existence.md ├── the-api.md └── walking-a-directory-tree.md ├── chap16 ├── account-hierarchy.png ├── chap16.ditamap ├── defgeneric.md ├── defmethod.md ├── generic-functions-and-classes.md ├── generic-functions-and-methods.md ├── method-combination.md └── object-reorientation.md └── pdf.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | /out 3 | /out/ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | pdf: 2 | dita -i docsrc/book.ditamap -f pdf --propertyfile=docsrc/pdf.properties -v 3 | 4 | html5: 5 | dita -i docsrc/book.ditamap -f html5 -v 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practical Common Lisp (中英文对照版) 2 | 3 | 预编译的 HTML 版在这里:[https://binghe.github.io/pcl-cn/] 4 | 5 | ## Requirements for building ebooks from (textual) sources 6 | 7 | - DITA-OT (3.3.2 or later) [https://www.dita-ot.org] 8 | 9 | ## Progress 10 | 11 | * [x] Chapter 01 12 | * [x] Chapter 02 13 | * [x] Chapter 03 14 | * [x] Chapter 04 15 | * [x] Chapter 05 16 | * [x] Chapter 06 17 | * [x] Chapter 07 18 | * [x] Chapter 08 19 | * [x] Chapter 09 20 | * [x] Chapter 10 21 | * [x] Chapter 11 22 | * [x] Chapter 12 23 | * [x] Chapter 13 24 | * [x] Chapter 14 25 | * [x] Chapter 15 26 | * [ ] Chapter 16 27 | * [ ] Chapter 17 28 | * [ ] Chapter 18 29 | * [ ] Chapter 19 30 | * [ ] Chapter 20 31 | * [ ] Chapter 21 32 | * [ ] Chapter 22 33 | * [ ] Chapter 23 34 | * [ ] Chapter 24 35 | * [ ] Chapter 25 36 | * [ ] Chapter 26 37 | * [ ] Chapter 27 38 | * [ ] Chapter 28 39 | * [ ] Chapter 29 40 | * [ ] Chapter 30 41 | * [ ] Chapter 31 42 | * [ ] Chapter 32 43 | * [ ] Fixing cross-references 44 | * [ ] Adding footnotes 45 | 46 | ## References 47 | 48 | - Source Code for _Practical Common Lisp_ (2005) [https://github.com/Apress/practical-common-lisp] 49 | - Source Code for _Practical Common Lisp_ (2011) [https://github.com/Apress/practical-common-lisp- 50 | 51 | ## Copyrights 52 | 53 | ``` 54 | Copyright(c) 2010-2011,2019 Chun Tian (binghe) 55 | 56 | Copyright(c) 2003-2005 Peter Seibel 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/chap02/experimenting.html: -------------------------------------------------------------------------------- 1 | 3 | Experimenting in the REPL(体验 REPL)

Experimenting in the REPL(体验 REPL)

To try the REPL, you need a Lisp expression that can be read, evaluated, and printed. One of the simplest kinds of Lisp expressions is a number. At the Lisp prompt, you can type 10 followed by Return and should see something like this:

为了测试 REPL,需要一个可以被读取、求值和打印的 Lisp 表达式。最简单类型的 4 | Lisp 表达式是一个数。在 Lisp 提示符下,可以输入 10 5 | 接着敲回车键,然后看到类似下面的东西:

CL-USER> 10
 6 | 10

The first 10 is the one you typed. The Lisp reader, the R in REPL, reads the text "10" and creates a Lisp object representing the number 10. This object is a self-evaluating object, which means that when given to the evaluator, the E in REPL, it evaluates to itself. This value is then given to the printer, which prints the 10 on the line by itself. While that may seem like a lot of work just to get back to where you started, things get a bit more interesting when you give Lisp something meatier to chew on. For instance, you can type (+ 2 3) at the Lisp prompt.

第一个 10 是你输入的。Lisp 读取器,即 REPL 中的 R,读取文本 “10” 7 | 并创建一个代表数字 10 的 Lisp 8 | 对象。这个对象是一个自求值(self-evaluating)对象,也就是说当把它送给求值器,即 9 | REPL 中的 E 以后,它将对其自身求值。这个值随后被送到打印器里,打印出只有 10 | 10 的那行来。整个过程看起来似乎是费了九牛二虎之力却回到了原点,但如果你给了 11 | Lisp 更有意义的信息,那么事情就变得有意思一些了。比如说,可以在 Lisp 12 | 提示符下输入 (+ 2 3)

CL-USER> (+ 2 3)
13 | 5

Anything in parentheses is a list, in this case a list of three 14 | elements, the symbol +, and the numbers 2 and 3. Lisp, in general, 15 | evaluates lists by treating the first element as the name of a 16 | function and the rest of the elements as expressions to be evaluated 17 | to yield the arguments to the function. In this case, the symbol + 18 | names a function that performs addition. 2 and 3 evaluate to 19 | themselves and are then passed to the addition function, which 20 | returns 5. The value 5 is passed to the printer, which prints it. Lisp 21 | can evaluate a list expression in other ways, but we needn't get into 22 | them right away. First we have to write...

小括号里的东西构成了一个列表,上述列表包括三个元素:符号 +,以及数字 23 | 2 和 3。一般来说,Lisp 对列表求值的时候会将第一个元素视为一个函数的名字,而其他元素作为即将求值的表达式则形成了该函数的实参。在本例里,符号 24 | + 是加法函数的名字。2 和 3 对自身求值后被传递给加法函数,从而返回了 25 | 5。返回值 5 被传递给打印器从而得以输出。Lisp 26 | 也可能以其他方式对列表求值,但我们现在还没必要讨论它。但从 Hello World 开始。

-------------------------------------------------------------------------------- /docs/chap03/wrapping-up.html: -------------------------------------------------------------------------------- 1 | 3 | Wrapping Up(总结)

Wrapping Up(总结)

Now, an interesting thing has happened. You removed duplication and 4 | made the code more efficient and more general at the same time. That's 5 | often the way it goes with a well-chosen macro. This makes sense 6 | because a macro is just another mechanism for creating 7 | abstractions--abstraction at the syntactic level, and abstractions are 8 | by definition more concise ways of expressing underlying 9 | generalities. Now the only code in the mini-database that's specific 10 | to CDs and the fields in them is in the make-cd, prompt-for-cd, and 11 | add-cd functions. In fact, our new where macro would work with any 12 | plist-based database.

现在,有趣的事情发生了。你不但去除了重复,而且还使得代码更有效且更通用了。这通常就是正确选用宏所达到的效果。这件事合乎逻辑,是因为宏只不过是另一种创建抽象的手法——词法层面的抽象,以及按照定义通过更简明地表达底层一般性的方式所得到的抽象。现在这个微型数据库的代码中只有 13 | make-cdprompt-for-cd 以及 add-cd 14 | 函数是特定于 CD 及其字段的。事实上,新的 where 宏可以用在任何基于 15 | plist 的数据库上。

However, this is still far from being a complete database. You can 16 | probably think of plenty of features to add, such as supporting 17 | multiple tables or more elaborate queries. In Chapter 27 we'll build 18 | an MP3 database that incorporates some of those features.

尽管如此,它距离一个完整的数据库仍很遥远。你可能会想到还有大量需要增加的特性,包括支持多表或是更复杂的查询。第 19 | 27 章将建立一个具备这些特性的 MP3 数据库。

The point of this chapter was to give you a quick introduction to just 20 | a handful of Lisp's features and show how they're used to write code 21 | that's a bit more interesting than "hello, world." In the next chapter 22 | we'll begin a more systematic overview of Lisp.

本章的要点在于快速介绍少量 Lisp 特性,展示如何用它们编写出比 “hello, 23 | world” 更有趣一点儿的代码。在下一章里,我们将对 Lisp 做一个更加系统的概述。

-------------------------------------------------------------------------------- /docs/chap04/function-calls.html: -------------------------------------------------------------------------------- 1 | 3 | Function Calls(函数调用)

Function Calls(函数调用)

The evaluation rule for function call forms is simple: evaluate the 4 | remaining elements of the list as Lisp forms and pass the resulting 5 | values to the named function. This rule obviously places some 6 | additional syntactic constraints on a function call form: all the 7 | elements of the list after the first must themselves be well-formed 8 | Lisp forms. In other words, the basic syntax of a function call form 9 | is as follows, where each of the arguments is itself a Lisp form:

函数调用形式的求值规则很简单:对列表的其余元素作为 Lisp 10 | 形式进行求值并将结果传递到命名函数中。这一规则明显的有着一些附加的句法限制在函数调用形式上:除第一个以外,所有的列表元素它们自身必须是一个形态良好的 11 | Lisp 形式。换句话说,函数调用形式的基本语法应如下所示,其中每个参数其本身也是一个 12 | Lisp 形式:

(function-name argument*)

Thus, the following expression is evaluated by first evaluating 1, 13 | then evaluating 2, and then passing the resulting values to the + 14 | function, which returns 3:

这样,下面这个表达式在求值时将首先求值 1,再求值 2,然后将得到的值传给 15 | + 函数,再返回 3:

(+ 1 2)

A more complex expression such as the following is evaluated in 16 | similar fashion except that evaluating the arguments (+ 1 2) and 17 | (- 3 4) entails first evaluating their arguments and applying the 18 | appropriate functions to them:

像下面这样更复杂的表达式也采用相似的求值方法,不过在求值参数 (+ 1 2) 19 | 和 (- 3 4) 时需要先对它们的参数求值,然后再对它们应用相应的函数:

(* (+ 1 2) (- 3 4))

Eventually, the values 3 and -1 are passed to the * function, which 20 | returns -3.

最后,值 3 和 -1 被传递到 * 函数里,从而得到 -3。

As these examples show, functions are used for many of the things that 21 | require special syntax in other languages. This helps keep Lisp's 22 | syntax regular.

正如这些例子所显示的这样,许多其他语言中需要特殊语法来处理的事务在 Lisp 23 | 中都可用函数来处理。Lisp 的这种设计对于保持其语法正则化很有帮助。

-------------------------------------------------------------------------------- /docs/chap04/syntax-and-semantics.html: -------------------------------------------------------------------------------- 1 | 3 | Syntax and Semantics(语法和语义)

Syntax and Semantics(语法和语义)

After that whirlwind tour, we'll settle down for a few chapters to 4 | take a more systematic look at the features you've used so far. I'll 5 | start with an overview of the basic elements of Lisp's syntax and 6 | semantics, which means, of course, that I must first address that 7 | burning question...

在一阵旋风似的介绍之后,我们将坐下来用几章的篇幅对已经用到的那些特性来一次更加系统化的介绍。我将从对 8 | Lisp 的基本语法和语义元素的概述开始,这意味着我必须先回答下面问题……

-------------------------------------------------------------------------------- /docs/chap04/whats-with-all.html: -------------------------------------------------------------------------------- 1 | 3 | What's with All the Parentheses?(括号里都可以有什么)

What's with All the Parentheses?(括号里都可以有什么)

Lisp's syntax is quite a bit different from the syntax of languages 4 | descended from Algol. The two most immediately obvious characteristics 5 | are the extensive use of parentheses and prefix notation. For whatever 6 | reason, a lot of folks are put off by this syntax. Lisp's detractors 7 | tend to describe the syntax as "weird" and "annoying." Lisp, they say, 8 | must stand for Lots of Irritating Superfluous Parentheses. Lisp folks, 9 | on the other hand, tend to consider Lisp's syntax one of its great 10 | virtues. How is it that what's so off-putting to one group is a source 11 | of delight to another?

Lisp 的语法和源自 Algol 12 | 的语言在语法上有很多不同。两者特征最明显的区别在于前者大量使用了括号和前缀表示法。说来也怪,大量的追随者都喜欢这样的语法。Lisp 13 | 的反对者们总是将这种语法描述成 “奇怪的” 和 “讨厌的”,他们说 Lisp 14 | 就是 “大量不合理的多余括号”(Lots of Irritating Superfluous 15 | Parentheses)的简称;而 Lisp 的追随者则认为,Lisp 16 | 的语法是它的最大优势。为什么两个团体之间会有如此对立的见解呢?

I can't really make the complete case for Lisp's syntax until I've 17 | explained Lisp's macros a bit more thoroughly, but I can start with an 18 | historical tidbit that suggests it may be worth keeping an open mind: 19 | when John McCarthy first invented Lisp, he intended to implement a 20 | more Algol-like syntax, which he called M-expressions. However, he 21 | never got around to it. He explained why not in his article "History 22 | of Lisp."

在本章不可能完整地描述 Lisp 的语法,因为我还没有彻底地解释 Lisp 23 | 的宏,但我可以从一些宝贵的历史经验来说明保持开放的思想是值得的:John McCarthy 24 | 首次发明 Lisp 时,曾想过实现一种类 Algol 的语法,他称之为 25 | M-表达式。尽管如此,他却从未实现这一点。他在自己的文章《History of Lisp》中对此加以解释。

The project of defining M-expressions precisely and compiling them 26 | or at least translating them into S-expressions was neither 27 | finalized nor explicitly abandoned. It just receded into the 28 | indefinite future, and a new generation of programmers appeared who 29 | preferred S-expressions to any FORTRAN-like or ALGOL-like notation 30 | that could be devised.

这个精确定义 M-表达式以及将其编译或至少转译成 31 | S-表达式的工程,既没有完成也没有明确放弃,它只不过是被无限期地推迟了。(相比 32 | S-表达式)更加偏爱类 FORTRAN 或类 Algol 表示法的程序员新一代也许会最终实现它。

In other words, the people who have actually used Lisp over the past 33 | 45 years have liked the syntax and have found that it makes the 34 | language more powerful. In the next few chapters, you'll begin to see 35 | why.

换句话说,过去 45 年以来,实际使用 Lisp 36 | 的人们已经喜欢上了这种语法,并且发现它能使该语言变得更为强大。接下来的几章里将告诉你为什么会这样。

-------------------------------------------------------------------------------- /docs/chap05/function-parameter-lists.html: -------------------------------------------------------------------------------- 1 | 3 | Function Parameter Lists(函数形参列表)

Function Parameter Lists(函数形参列表)

There's not a lot more to say about function names or documentation 4 | strings, and it will take a good portion of the rest of this book to 5 | describe all the things you can do in the body of a function, which 6 | leaves us with the parameter list.

关于函数名或文档字符串就没有更多可说的了,而本书其余部分将用很多篇幅来描述所有可在一个函数体里做的事情,因此就只需讨论形参列表了。

The basic purpose of a parameter list is, of course, to declare the 7 | variables that will receive the arguments passed to the function. When 8 | a parameter list is a simple list of variable names--as in 9 | verbose-sum--the parameters are called required parameters. When a 10 | function is called, it must be supplied with one argument for every 11 | required parameter. Each parameter is bound to the corresponding 12 | argument. If a function is called with too few or too many arguments, 13 | Lisp will signal an error.

很明显,一个形参列表的基本用途是为了声明一些变量,用来接收传递给函数的实参。当形参列表是一个由变量名所组成的简单列表时,如同在 14 | verbose-sum 里那样,这些形参被称为必要形参。当函数被调用时,必须为它的每一个必要形参都提供一个实参。每一个形参被绑定到对应的实参上。如果一 15 | 个函数以过少或过多的实参来调用的话,Lisp 就会报错。

However, Common Lisp's parameter lists also give you more flexible 16 | ways of mapping the arguments in a function call to the function's 17 | parameters. In addition to required parameters, a function can have 18 | optional parameters. Or a function can have a single parameter that's 19 | bound to a list containing any extra arguments. And, finally, 20 | arguments can be mapped to parameters using keywords rather than 21 | position. Thus, Common Lisp's parameter lists provide a convenient 22 | solution to several common coding problems.

但是,Common Lisp 23 | 的形参列表也给了你更灵活的方式将函数调用实参映射到函数形参。除了必要形参以外,一个函数还可以有可选形参,或是也可以用单一形参绑定到含有任意多个额外参数的列表上。最后,参数还可以通过关键字而不是位置来映射到形参上。这样,Common 24 | Lisp 的形参列表对于几种常见的编码问题提供了一种便利的解决方案。

-------------------------------------------------------------------------------- /docs/chap08/beyond-simple-macros.html: -------------------------------------------------------------------------------- 1 | 3 | Beyond Simple Macros(更复杂的宏)

Beyond Simple Macros(更复杂的宏)

I could, of course, say a lot more about macros. All the macros you've 4 | seen so far have been fairly simple examples that save you a bit of 5 | typing but don't provide radical new ways of expressing things. In 6 | upcoming chapters you'll see examples of macros that allow you to 7 | express things in ways that would be virtually impossible without 8 | macros. You'll start in the very next chapter, in which you'll build a 9 | simple but effective unit test framework.

当然,我可以说更多关于宏的事情。目前为止,所有你见到的宏都是相当简单的例子,它们帮助你减轻了一些写代码的工作量,但却并没有提供表达事物的根本性的新方式。在接下来的章节里你将看到一些宏的示例,它们允许你以一种假如没有宏就完全做不到的方式来表达事物。从下一章开始,你将构建一个简单而高效的单元测试框架。

-------------------------------------------------------------------------------- /docs/chap08/once-only.html: -------------------------------------------------------------------------------- 1 | 3 | Another classic macro-writing MACRO: ONCE-ONLY(另一个经典的用于编写宏的宏:ONCE-ONLY)

Another classic macro-writing MACRO: ONCE-ONLY(另一个经典的用于编写宏的宏:ONCE-ONLY

Another classic macro-writing macro is once-only, which is used to 4 | generate code that evaluates certain macro arguments once only and in 5 | a particular order. Using once-only, you could write do-primes almost 6 | as simply as the original leaky version, like this:

另一个经典的用于编写宏的宏是 7 | once-only,它用来生成以特定顺序仅求值特定宏参数一次的代码。使用 8 | once-only,你几乎可以跟最初的有漏洞版本一样简单地写出 do-primes 9 | 来,就像这样:

(defmacro do-primes ((var start end) &body body)
10 |   (once-only (start end)
11 |     `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
12 |          ((> ,var ,end))
13 |        ,@body)))

However, the implementation of once-only is a bit too involved for a 14 | blow-by-blow explanation, as it relies on multiple levels of 15 | backquoting and unquoting. If you really want to sharpen your macro 16 | chops, you can try to figure out how it works. It looks like this:

尽管如此,但如果详加解释的话,once-only 17 | 的实现将远远超出本章的内容,因为它依赖于多层的反引用和解引用。如果真想进一步提高宏技术的话,你可以尝试分析它的工作方式。如下所示:

(defmacro once-only ((&rest names) &body body)
18 |   (let ((gensyms (loop for n in names collect (gensym))))
19 |     `(let (,@(loop for g in gensyms collect `(,g (gensym))))
20 |       `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
21 |         ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
22 |            ,@body)))))
-------------------------------------------------------------------------------- /docs/chap10/higher-math.html: -------------------------------------------------------------------------------- 1 | 3 | Higher Math(高等数学)

Higher Math(高等数学)

The functions you've seen so far are the beginning of the built-in 4 | mathematical functions. Lisp also supports logarithms: LOG; 5 | exponentiation: EXP and EXPT; the basic trigonometric functions: SIN, 6 | COS, and TAN; their inverses: ASIN, ACOS, and 7 | ATAN; hyperbolic 8 | functions: SINH, COSH, and TANH; and their inverses: ASINH, ACOSH, and 9 | ATANH. It also provides functions to get at the individual bits of an 10 | integer and to extract the parts of a ratio or a complex number. For a 11 | complete list, see any Common Lisp reference.

目前为止,你所看到的函数只是初级的内置数学函数。Lisp 12 | 也支持对数函数:LOG,指数函数:EXPEXPT,基本三角函数:SINCOS 13 | 和 TAN 及其它们的逆:ASINACOS 和 14 | ATAN,双曲函数:SINHCOSHTANH 15 | 以及它们的逆:ASINHACOSH 和 16 | ATANH。它还提供了函数用来获取一个整数中单独的位以及取出一个比值或一个复数中的部分。完整的函数列表参见任何 17 | Common Lisp 参考。

-------------------------------------------------------------------------------- /docs/chap11/sequence-predicates.html: -------------------------------------------------------------------------------- 1 | 3 | Sequence Predicates(序列谓词)

Sequence Predicates(序列谓词)

Four other handy functions are EVERY, SOME, NOTANY, and NOTEVERY, 4 | which iterate over sequences testing a boolean predicate. The first 5 | argument to all these functions is the predicate, and the remaining 6 | arguments are sequences. The predicate should take as many arguments 7 | as the number of sequences passed. The elements of the sequences are 8 | passed to the predicate--one element from each sequence--until one of 9 | the sequences runs out of elements or the overall termination test is 10 | met: EVERY terminates, returning false, as soon as the predicate 11 | fails. If the predicate is always satisfied, it returns true. SOME 12 | returns the first non-NIL value returned by the predicate or returns 13 | false if the predicate is never satisfied. NOTANY returns false as 14 | soon as the predicate is satisfied or true if it never is. And 15 | NOTEVERY returns true as soon as the predicate fails or false if the 16 | predicate is always satisfied. Here are some examples of testing just 17 | one sequence:

另外四个常见的函数是 EVERYSOMENOTANY 和 18 | NOTEVERY,它们在序列上迭代并测试一个布尔谓词。所有这些函数的第一参数是一个谓词,其余的参数都是序列。这个谓词应当接受与所传递序列相同数量的参数。序列的元素被传递给该谓词,每个序列中各取出一个元素,直到某个序列用完所有的元素或是整体终止测试条件被满足:EVERY 19 | 在谓词失败时返回假。如果谓词总被满足,它返回真。SOME 20 | 返回由谓词所返回的第一个非NIL值,或者在谓词永远得不到满足时返回假。NOTANY 21 | 将在谓词满足时返回假,或者在从未满足时返回真。而 22 | NOTEVERY 23 | 在谓词失败时返回真,或是在谓词总是满足时返回假。下面是一些仅测试在一个序列上的例子:

(every #'evenp #(1 2 3 4 5))    ==> NIL
24 | (some #'evenp #(1 2 3 4 5))     ==> T
25 | (notany #'evenp #(1 2 3 4 5))   ==> NIL
26 | (notevery #'evenp #(1 2 3 4 5)) ==> T

These calls compare elements of two sequences pairwise:

下面的调用比较成对的两个序列中的元素:

(every #'> #(1 2 3 4) #(5 4 3 2))    ==> NIL
27 | (some #'> #(1 2 3 4) #(5 4 3 2))     ==> T
28 | (notany #'> #(1 2 3 4) #(5 4 3 2))   ==> NIL
29 | (notevery #'> #(1 2 3 4) #(5 4 3 2)) ==> T
-------------------------------------------------------------------------------- /docs/chap11/vector-as-sequences.html: -------------------------------------------------------------------------------- 1 | 3 | Vectors As Sequences(作为序列的向量)

Vectors As Sequences(作为序列的向量)

As mentioned earlier, vectors and lists are the two concrete subtypes 4 | of the abstract type sequence. All the functions I'll discuss in the 5 | next few sections are sequence functions; in addition to being 6 | applicable to vectors--both general and specialized--they can also be 7 | used with lists.

正如早先所提到的,向量和列表是抽象类型序列的两种具体子类型。接下来几节里讨论的所有函数都是序列函数:除了可以应用于向量(无论是通用还是特化的)之外,它们还可应用于列表。

The two most basic sequence functions are LENGTH, which returns the 8 | length of a sequence, and ELT, which allows you to access individual 9 | elements via an integer index. LENGTH takes a sequence as its only 10 | argument and returns the number of elements it contains. For vectors 11 | with a fill pointer, this will be the value of the fill pointer. ELT, 12 | short for element, takes a sequence and an integer index between zero 13 | (inclusive) and the length of the sequence (exclusive) and returns the 14 | corresponding element. ELT will signal an error if the index is out of 15 | bounds. Like LENGTH, ELT treats a vector with a fill pointer as having 16 | the length specified by the fill pointer.

两个最基本的序列函数是 17 | LENGTH,其返回一个序列的长度;ELT,其允许通过一个整数索引来访问个别元素。LENGTH 18 | 接受序列作为其唯一的参数并返回它含有的元素数量。对于带有填充指针的向量,这些是填充指针的值。ELT 19 | 是元素(element)的简称,它接受序列和从 0 20 | 到序列长度(左闭右开区间)的整数索引并返回对应的元素。ELT 21 | 将在索引超出边界时报错。和 LENGTH 一样,ELT 22 | 也将把一个带有填充指针的向量视为其具有该填充指针所指定的长度。

(defparameter *x* (vector 1 2 3))
23 | 
24 | (length *x*) ==> 3
25 | (elt *x* 0)  ==> 1
26 | (elt *x* 1)  ==> 2
27 | (elt *x* 2)  ==> 3
28 | (elt *x* 3)  ==> error

ELT is also a SETFable place, so you can set the value of a 29 | particular element like this:

ELT 也是一个支持 SETF 的位置,因此可以像这样来设置一个特定元素的值:

(setf (elt *x* 0) 10)
30 | 
31 | *x* ==> #(10 2 3)
-------------------------------------------------------------------------------- /docs/chap11/whole-sequence-manipulations.html: -------------------------------------------------------------------------------- 1 | 3 | Whole Sequence Manipulations(针对整个序列的操作)

Whole Sequence Manipulations(针对整个序列的操作)

A handful of functions perform operations on a whole sequence (or 4 | sequences) at a time. These tend to be simpler than the other 5 | functions I've described so far. For instance, COPY-SEQ and REVERSE 6 | each take a single argument, a sequence, and each returns a new 7 | sequence of the same type. The sequence returned by COPY-SEQ contains 8 | the same elements as its argument while the sequence returned by 9 | REVERSE contains the same elements but in reverse order. Note that 10 | neither function copies the elements themselves--only the returned 11 | sequence is a new object.

有一些函数可以一次在整个序列(或多个序列)上进行操作。这些函数比目前为止我已描述的其他函数简单一些。例如,COPY-SEQ 12 | 和 REVERSE 都接受单一的序列参数并返回一个相同类型的新序列。COPY-SEQ 13 | 返回的序列包含与其参数相同的元素,而 REVERSE 14 | 返回的序列则含有顺序相反的相同元素。注意,这两个函数都不会复制元素本身,只有返回的序列是一个新对象。

The CONCATENATE function creates a new sequence containing the 15 | concatenation of any number of sequences. However, unlike REVERSE and 16 | COPY-SEQ, which simply return a sequence of the same type as their 17 | single argument, CONCATENATE must be told explicitly what kind of 18 | sequence to produce in case the arguments are of different types. Its 19 | first argument is a type descriptor, like the :element-type argument 20 | to MAKE-ARRAY. In this case, the type descriptors you'll most likely 21 | use are the symbols VECTOR, LIST, or STRING. For example:

函数 CONCATENATE 22 | 创建一个将任意数量序列连接在一起的新序列。不过,跟 REVERSECOPY-SEQ 23 | 简单地返回与其单一参数相同类型序列有所不同的是,CONCATENATE 24 | 必须被显式指定产生何种类型的序列,因为其参数可能是不同类型的。它的第一个参数是类型描述符,就像是 25 | MAKE-ARRAY:element-type 26 | 参数那样。这里最常用到的类型描述符是符号 VECTORLIST 27 | 和 STRING。 例如:

(concatenate 'vector #(1 2 3) '(4 5 6))    ==> #(1 2 3 4 5 6)
28 | (concatenate 'list #(1 2 3) '(4 5 6))      ==> (1 2 3 4 5 6)
29 | (concatenate 'string "abc" '(#\d #\e #\f)) ==> "abcdef"
-------------------------------------------------------------------------------- /docs/chap12/after-append.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap12/after-append.png -------------------------------------------------------------------------------- /docs/chap12/list-1-2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap12/list-1-2-3.png -------------------------------------------------------------------------------- /docs/chap12/mixed-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap12/mixed-list.png -------------------------------------------------------------------------------- /docs/chap12/one-cons-cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap12/one-cons-cell.png -------------------------------------------------------------------------------- /docs/chap12/other-structures.html: -------------------------------------------------------------------------------- 1 | 3 | Other Structures(其他结构)

Other Structures(其他结构)

While cons cells and lists are typically considered to be synonymous, 4 | that's not quite right--as I mentioned earlier, you can use lists of 5 | lists to represent trees. Just as the functions discussed in this 6 | chapter allow you to treat structures built out of cons cells as 7 | lists, other functions allow you to use cons cells to represent trees, 8 | sets, and two kinds of key/value maps. I'll discuss some of those 9 | functions in the next chapter.

尽管点对单元和列表通常被视作同义词,但这并不很准确。正如我早先提到的,你可以使用列表的列表来表示树。正如本章所讨论的函数允许你将构建在点对单元上的结构视为列表那样,其他函数也允许你使用点对单元来表示树、集合以及两类键/值映射表。我将在第 10 | 13 章讨论这类函数中的一些。

-------------------------------------------------------------------------------- /docs/chap13/alist-abc-123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap13/alist-abc-123.png -------------------------------------------------------------------------------- /docs/chap13/beyond-lists.html: -------------------------------------------------------------------------------- 1 | 3 | Beyond Lists: Other Uses for Cons Cells(超越列表:点对单元的其他用法)

Beyond Lists: Other Uses for Cons Cells(超越列表:点对单元的其他用法)

As you saw in the previous chapter, the list data type is an illusion 4 | created by a set of functions that manipulate cons cells. Common Lisp 5 | also provides functions that let you treat data structures built out 6 | of cons cells as trees, sets, and lookup tables. In this chapter I'll 7 | give you a quick tour of some of these other data structures and the 8 | functions for manipulating them. As with the list-manipulation 9 | functions, many of these functions will be useful when you start 10 | writing more complicated macros and need to manipulate Lisp code as 11 | data.

如同你在前面章节里看到的,列表数据类型是由一组操作点对单元的函数描述的。另外,Common 12 | Lisp 13 | 还提供了一些函数;它们可使你把点对单元构建出的数据结构看作树、集合及查询表。本章将简要介绍这其中的一些数据结构及其处理函数。和列表处理函数一样,在开始编写更复杂的宏以及需要将 14 | Lisp 代码作为数据处理时,这其中有很多函数会很有用。

-------------------------------------------------------------------------------- /docs/chap13/copy-list-list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap13/copy-list-list-or-tree.png -------------------------------------------------------------------------------- /docs/chap13/copy-tree-list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap13/copy-tree-list-or-tree.png -------------------------------------------------------------------------------- /docs/chap13/list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap13/list-or-tree.png -------------------------------------------------------------------------------- /docs/chap13/plist-abc-123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap13/plist-abc-123.png -------------------------------------------------------------------------------- /docs/chap14/bulk-reads.html: -------------------------------------------------------------------------------- 1 | 3 | Bulk Reads(批量读取)

Bulk Reads(批量读取)

One last reading function, READ-SEQUENCE, works with both character 4 | and binary streams. You pass it a sequence (typically a vector) and a 5 | stream, and it attempts to fill the sequence with data from the 6 | stream. It returns the index of the first element of the sequence that 7 | wasn't filled or the length of the sequence if it was able to 8 | completely fill it. You can also pass :start and :end keyword 9 | arguments to specify a subsequence that should be filled instead. The 10 | sequence argument must be a type that can hold elements of the 11 | stream's element type. Since most operating systems support some form 12 | of block I/O, READ-SEQUENCE is likely to be quite a bit more efficient 13 | than filling a sequence by repeatedly calling READ-BYTE or READ-CHAR.

最后一个读取函数 READ-SEQUENCE 14 | 可同时工作在字符和二进制流上。你传递给它一个序列(通常是一个向量)和一个流,然后它会尝试用来自流的数据填充该序列。它返回序列中第一个没有被填充的元素的索引,或是在完全填充的情况下返回该序列的长度。你也可以传递 15 | :start:end 16 | 关键字参数来指定一个应当被代替填充的子序列。该序列参数的元素类型必须足 17 | 以保存带有该流元素类型的元素。由于多数操作系统支持某种形式的块 18 | I/O,READ-SEQUENCE 通常比重复调用 19 | READ-BYTEREAD-CHAR 来填充一个序列更加高效。

-------------------------------------------------------------------------------- /docs/chap14/reading-binary-data.html: -------------------------------------------------------------------------------- 1 | 3 | Reading Binary Data(读取二进制数据)

Reading Binary Data(读取二进制数据)

By default OPEN returns character streams, which translate the 4 | underlying bytes to characters according to a particular 5 | character-encoding scheme. To read the raw bytes, you need to pass 6 | OPEN an :element-type argument of '(unsigned-byte 8). You can pass 7 | the resulting stream to the function READ-BYTE, which will return an 8 | integer between 0 and 255 each time it's called. READ-BYTE, like the 9 | character-reading functions, also accepts optional arguments to 10 | specify whether it should signal an error if called at the end of the 11 | file and what value to return if not. In Chapter 24 you'll build a 12 | library that allows you to conveniently read structured binary data 13 | using READ-BYTE.

默认情况下,OPEN 14 | 返回字符流,它根据特定的字符编码方案将底层字节转化成字符。 为了读取原 15 | 始字节,你需要向 16 | OPEN 传递一个值为 '(unsigned-byte 8):element-type 17 | 参数。 你可以将得到的流传给 18 | READ-BYTE,它将在每次被调用时返回一个从 0 到 255 之间的整数。与字符读取函数一样,READ-BYTE 19 | 也支持可选的参数此便指定当其被调用在文件结尾时是否应该报错,以及在遇到结尾时返回何值。在第 20 | 24 章里你将构建一个库,它允许使用 READ-BYTE 21 | 来便利地读取结构化的二进制数据。

-------------------------------------------------------------------------------- /docs/chap15/packing-the-library.html: -------------------------------------------------------------------------------- 1 | 3 | Packaging the Library(给库打包)

Packaging the Library(给库打包)

Speaking of packages, if you download the complete code for this 4 | library, you'll see that it's defined in a new package, 5 | com.gigamonkeys.pathnames. I'll discuss the details of defining and 6 | using packages in Chapter 21. For now you should note that some 7 | implementations provide their own packages that contain functions with 8 | some of the same names as the ones you'll define in this chapter and 9 | make those names available in the CL-USER package. Thus, if you try to 10 | define the functions from this library while in the CL-USER package, 11 | you may get errors or warnings about clobbering existing 12 | definitions. To avoid this possibility, you can create a file called 13 | packages.lisp with the following contents:

从包的角度讲,如果你下载了该库的完整代码,会看到它被定义在一个新的包 14 | com.gigamonkeys.pathnames 中。我将在第 21 15 | 章讨论定义使用包的细节。目前你应当注意,某些实现提供了它们自己的包,其中含有一些函数与你将在本章中定义的一些函数有相同的名字,并且这些名字可在 16 | CL-USER 包中访问。这样,如果你试图在 CL-USER 17 | 包中定义该库中的某些函数,可能会得到关于破坏了已有定义的错误或警告。为了避免发生这种情况,你可以创建一个称为 18 | packages.lisp 的文件,其中带有下面的内容:

(in-package :cl-user)
19 | 
20 | (defpackage :com.gigamonkeys.pathnames
21 |   (:use :common-lisp)
22 |   (:export
23 |    :list-directory
24 |    :file-exists-p
25 |    :directory-pathname-p
26 |    :file-pathname-p
27 |    :pathname-as-directory
28 |    :pathname-as-file
29 |    :walk-directory
30 |    :directory-p
31 |    :file-p))

and LOAD it. Then at the REPL or at the top of the file where you type 32 | the definitions from this chapter, type the following expression:

并加载它,然后在 REPL 中或者在你输入定义的文件顶端,输入下列表达式:

(in-package :com.gigamonkeys.pathnames)

In addition to avoiding name conflicts with symbols already available 33 | in CL-USER, packaging the library this way also makes it easier to use 34 | in other code, as you'll see in several future chapters.

将库以这种方形式打包,除了可以避免与那些已存在于 CL-USER 35 | 包中的符号产生冲突以外,还可以使其更容易被其他代码使用,你在后续几章中将看到这一点。

-------------------------------------------------------------------------------- /docs/chap15/the-api.html: -------------------------------------------------------------------------------- 1 | 3 | The API(应用程序接口)

The API(应用程序接口)

The basic operations the library will support will be getting a list 4 | of files in a directory and determining whether a file or directory 5 | with a given name exists. You'll also write a function for recursively 6 | walking a directory hierarchy, calling a given function for each 7 | pathname in the tree.

该库支持的基本操作是获取目录中的文件列表,并检测给定名字的文件或目录是否存在。你也将编写函数用于递归遍历目录层次,并在目录树的每个路径名上调用给定的函数。

In theory, these directory listing and file existence operations are 8 | already provided by the standard functions DIRECTORY and 9 | PROBE-FILE. However, as you'll see, there are enough different ways to 10 | implement these functions--all within the bounds of valid 11 | interpretations of the language standard--that you'll want to write 12 | new functions that provide a consistent behavior across 13 | implementations.

从理论上来讲,这些列目录和测试文件存在性的操作已经由标准函数 DIRECTORY 14 | 和 PROBE-FILE 15 | 提供了。不过正如你将看到的那样,会有许多不同的方式来实现这些函数──所有这些均属于语言标准的有效解释,因此你希望编写新的函数,以在不同实现间提供一致的行为。

-------------------------------------------------------------------------------- /docs/chap16/account-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docs/chap16/account-hierarchy.png -------------------------------------------------------------------------------- /docsrc/.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | *.DS_Store 3 | -------------------------------------------------------------------------------- /docsrc/book.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Practical Common Lisp 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docsrc/chap01/chap01.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docsrc/chap01/who-is-this-book-for.md: -------------------------------------------------------------------------------- 1 | # Who This Book Is For(本书面向的读者) 2 | 3 | This book is for you if you're curious about Common Lisp, regardless 4 | of whether you're already convinced you want to use it or if you just 5 | want to know what all the fuss is about. 6 | 7 | 如果你对 Common Lisp 8 | 感兴趣,那么无论是否已经确定要使用它或者只是想一窥其门径,本书都挺适合你的。 9 | 10 | If you've learned some Lisp already but have had trouble making the 11 | leap from academic exercises to real programs, this book should get 12 | you on your way. On the other hand, you don't have to be already 13 | convinced that you want to use Lisp to get something out of this book. 14 | 15 | 如果你已经学会了一些 16 | Lisp,但却难于跨越从学术训练到真实程序之间的鸿沟,那么本书刚好可以帮你走上正途。而另一方面,你也不必带着学以致用的目的来阅读本书。 17 | 18 | If you're a hard-nosed pragmatist who wants to know what advantages 19 | Common Lisp has over languages such as Perl, Python, Java, C, or C#, 20 | this book should give you some ideas. Or maybe you don't even care 21 | about using Lisp--maybe you're already sure Lisp isn't really any 22 | better than other languages you know but are annoyed by some Lisper 23 | telling you that's because you just don't "get it." If so, this book 24 | will give you a straight-to-the-point introduction to Common Lisp. If, 25 | after reading this book, you still think Common Lisp is no better than 26 | your current favorite languages, you'll be in an excellent position to 27 | explain exactly why. 28 | 29 | 如果你是个顽强的实用主义者,想知道 Common Lisp 相比 Perl、Python、Java、C 30 | 和 C# 这些语言都有哪些优势,那么本书应该可以提供一些思路。或者你根本就没打算使用 31 | Lisp——可能是因为已经确信 Lisp 32 | 并不比已知的其他语言更好,但却由于不熟悉它而无法反驳那些 Lisp 33 | 程序员。如果是这样,本书将给你一个直奔 Common Lisp 34 | 主题的介绍。如果在读完本书以后,你仍然认为 Common Lisp 35 | 比不上自己当前喜爱的其他语言,那么你也有充分理由来说明自己观点了。 36 | 37 | I cover not only the syntax and semantics of the language but also how 38 | you can use it to write software that does useful stuff. In the first 39 | part of the book, I'll cover the language itself, mixing in a few 40 | "practical" chapters, where I'll show you how to write real 41 | code. Then, after I've covered most of the language, including several 42 | parts that other books leave for you to figure out on your own, the 43 | remainder of the book consists of nine more practical chapters where 44 | I'll help you write several medium-sized programs that actually do 45 | things you might find useful: filter spam, parse binary files, catalog 46 | MP3s, stream MP3s over a network, and provide a Web interface for the 47 | MP3 catalog and server. 48 | 49 | 我不但介绍了该语言的语法和语义,还讲述了如何使用它来编写有用的软件。在本书的第一部分,我将谈及语言本身,同时穿插一些“实践性”章节,展示如何编写真实的代码。接着,在我阐述了该语言的绝大部分见容后——包括某些在其他书里往往留给你自己去考查的内容,将给出九个更实用的章节,帮助你编写一些中等大小的程序来做一些你可能认为有用的事:过滤垃圾邮件、解析二进制文件、分类 50 | MP3、通过网络播放 MP3 流媒体,以及为MP3目录和服务器提供 Web 接口。 51 | 52 | After you finish this book, you'll be familiar with all the most 53 | important features of the language and how they fit together, you'll 54 | have used Common Lisp to write several nontrivial programs, and you'll 55 | be well prepared to continue exploring the language on your own. While 56 | everyone's road to Lisp is different, I hope this book will help 57 | smooth the way for you. So, let's begin. 58 | 59 | 读完本书后,你将熟悉该语言的所有最重要的特性以及它们是如何组织在一起的,而是你已经用 60 | Common Lisp 写出了一些非凡的程序,并且可以凭借自身力量继续探索该语言的了。尽管每个人的 61 | Lisp 之路都有所不同,但我还是希望本书可以帮助你少走些弯路。那么,让我们就此上路吧。 62 | -------------------------------------------------------------------------------- /docsrc/chap02/chap02.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docsrc/chap02/experimenting.md: -------------------------------------------------------------------------------- 1 | # Experimenting in the REPL(体验 REPL) 2 | 3 | To try the REPL, you need a Lisp expression that can be read, evaluated, and printed. One of the simplest kinds of Lisp expressions is a number. At the Lisp prompt, you can type `10` followed by Return and should see something like this: 4 | 5 | 为了测试 REPL,需要一个可以被读取、求值和打印的 Lisp 表达式。最简单类型的 6 | Lisp 表达式是一个数。在 Lisp 提示符下,可以输入 `10` 7 | 接着敲回车键,然后看到类似下面的东西: 8 | 9 | ``` 10 | CL-USER> 10 11 | 10 12 | ``` 13 | 14 | The first `10` is the one you typed. The Lisp reader, the R in REPL, reads the text "10" and creates a Lisp object representing the number 10. This object is a self-evaluating object, which means that when given to the evaluator, the E in REPL, it evaluates to itself. This value is then given to the printer, which prints the 10 on the line by itself. While that may seem like a lot of work just to get back to where you started, things get a bit more interesting when you give Lisp something meatier to chew on. For instance, you can type `(+ 2 3)` at the Lisp prompt. 15 | 16 | 第一个 `10` 是你输入的。Lisp 读取器,即 REPL 中的 R,读取文本 “10” 17 | 并创建一个代表数字 10 的 Lisp 18 | 对象。这个对象是一个自求值(self-evaluating)对象,也就是说当把它送给求值器,即 19 | REPL 中的 E 以后,它将对其自身求值。这个值随后被送到打印器里,打印出只有 20 | 10 的那行来。整个过程看起来似乎是费了九牛二虎之力却回到了原点,但如果你给了 21 | Lisp 更有意义的信息,那么事情就变得有意思一些了。比如说,可以在 Lisp 22 | 提示符下输入 `(+ 2 3)`。 23 | 24 | ``` 25 | CL-USER> (+ 2 3) 26 | 5 27 | ``` 28 | 29 | Anything in parentheses is a list, in this case a list of three 30 | elements, the symbol `+`, and the numbers 2 and 3. Lisp, in general, 31 | evaluates lists by treating the first element as the name of a 32 | function and the rest of the elements as expressions to be evaluated 33 | to yield the arguments to the function. In this case, the symbol `+` 34 | names a function that performs addition. 2 and 3 evaluate to 35 | themselves and are then passed to the addition function, which 36 | returns 5. The value 5 is passed to the printer, which prints it. Lisp 37 | can evaluate a list expression in other ways, but we needn't get into 38 | them right away. First we have to write... 39 | 40 | 小括号里的东西构成了一个列表,上述列表包括三个元素:符号 `+`,以及数字 41 | 2 和 3。一般来说,Lisp 对列表求值的时候会将第一个元素视为一个函数的名字,而其他元素作为即将求值的表达式则形成了该函数的实参。在本例里,符号 42 | `+` 是加法函数的名字。2 和 3 对自身求值后被传递给加法函数,从而返回了 43 | 5。返回值 5 被传递给打印器从而得以输出。Lisp 44 | 也可能以其他方式对列表求值,但我们现在还没必要讨论它。但从 Hello World 开始。 45 | -------------------------------------------------------------------------------- /docsrc/chap02/free-your-mind.md: -------------------------------------------------------------------------------- 1 | # Free Your Mind: Interactive Programming(放开思想:交互式编程) 2 | 3 | When you start Lisp in a Box, you should see a buffer containing a prompt that looks like this: 4 | 5 | 当启动 Lisp-in-a-Box 时,应该可以看到一个带有类似下面提示符的缓冲区: 6 | 7 | ``` 8 | CL-USER> 10 9 | ``` 10 | 11 | This is the Lisp prompt. Like a Unix or DOS shell prompt, the Lisp 12 | prompt is a place where you can type expressions that will cause 13 | things to happen. However, instead of reading and interpreting a line 14 | of shell commands, Lisp reads Lisp expressions, evaluates them 15 | according to the rules of Lisp, and prints the result. Then it does it 16 | again with the next expression you type. That endless cycle of 17 | reading, evaluating, and printing is why it's called the 18 | read-eval-print loop, or REPL for short. It's also referred to as the 19 | top-level, the top-level listener, or the Lisp listener. 20 | 21 | 这是 Lisp 的提示符。就像 Unix 或 DOS shell 提示符那样,在 Lisp 22 | 提示符的位置上输入表达式可以产生一定的结果。尽管如此,Lisp 读取的是 23 | Lisp 表达式而非一行 shell 命令,按照 Lisp 24 | 的规则来对它求值,然后打印结果。接着它再继续处理你输入的下一个表达式。正是由于这种无休止的读取、求值和打印的周期变化,因此它被称为读-求值-打印循环(read-eval-print 25 | loop),简称 REPL。它也被称为顶层(top-level)、顶层监听器(top-level 26 | listener)或 Lisp 监听器。 27 | 28 | From within the environment provided by the REPL, you can define and 29 | redefine program elements such as variables, functions, classes, and 30 | methods; evaluate any Lisp expression; load files containing Lisp 31 | source code or compiled code; compile whole files or individual 32 | functions; enter the debugger; step through code; and inspect the 33 | state of individual Lisp objects. 34 | 35 | 借助 REPL 提供的环境就可以定义或重定义诸如变量、函数、类和方法等程序要素,求值任何 36 | Lisp 表达式,加载含有 Lisp 37 | 源代码或编译后代码的文件,编译整个文件或者单独的函数,进入调试器,单步调试代码,以及检查个别 38 | Lisp 对象的状态。 39 | 40 | All those facilities are built into the language, accessible via 41 | functions defined in the language standard. If you had to, you could 42 | build a pretty reasonable programming environment out of just the REPL 43 | and any text editor that knows how to properly indent Lisp code. But 44 | for the true Lisp programming experience, you need an environment, 45 | such as SLIME, that lets you interact with Lisp both via the REPL and 46 | while editing source files. For instance, you don't want to have to 47 | cut and paste a function definition from a source file to the REPL or 48 | have to load a whole file just because you changed one function; your 49 | Lisp environment should let us evaluate or compile both individual 50 | expressions and whole files directly from your editor. 51 | 52 | 所有这些机制都是语言内置的,可以通过语言标准所定义的函数来访问。如果有必要,你只需使用 53 | REPL 和一个知道如何正确缩进 Lisp 54 | 代码的文本编辑器,就可以构建出一个相当合理的编程环境来。但如果追求真正的 55 | Lisp 编程体验,则需要一个像 SLIME 这样的环境,它可以同时通过 56 | REPL 和编辑源文件时与 Lisp 进行交互。 57 | 例如,没有必要把一个函数定义从源文件里复制并粘贴到 REPL 58 | 里,也不必因为改变了一个函数就把整个文件重新加载。Lisp 59 | 环境应该允许编译单独的表达式和直接来自编辑器的整个文件或对其求值。 60 | -------------------------------------------------------------------------------- /docsrc/chap02/tour-of-the-repl.md: -------------------------------------------------------------------------------- 1 | # Lather, Rinse, Repeat: A Tour of the REPL(REPL 简介) 2 | 3 | In this chapter you'll set up your programming environment and write 4 | your first Common Lisp programs. We'll use the easy-to-install Lisp in 5 | a Box developed by Matthew Danish and Mikel Evins, which packages a 6 | Common Lisp implementation with Emacs, a powerful Lisp-aware text 7 | editor, and SLIME,1 a Common Lisp development environment built on top 8 | of Emacs. 9 | 10 | 本章将学习如何设置编程环境并编写第一个 Common Lisp 程序。我们将使用由 11 | Matthew Danish 和 Mikel Evins 开发的安装便利的 Lisp-in-a-Box 12 | 环境,它封装了带有 Emacs 强大且对 Lisp 较为友好的文本编辑器的 Common 13 | Lisp 实现,以及 SLIME(Superior Lisp Interaction Mode for Emacs)——构建在 14 | Emacs 之上的 Common Lisp 开发环境。 15 | 16 | This combo provides a state-of-the-art Common Lisp development 17 | environment that supports the incremental, interactive development 18 | style that characterizes Lisp programming. The SLIME environment has 19 | the added advantage of providing a fairly uniform user interface 20 | regardless of the operating system and Common Lisp implementation you 21 | choose. I'll use the Lisp in a Box environment in order to have a 22 | specific development environment to talk about; folks who want to 23 | explore other development environments such as the graphical 24 | integrated development environments (IDEs) provided by some of the 25 | commercial Lisp vendors or environments based on other editors 26 | shouldn't have too much trouble translating the basics. 27 | 28 | 上述组合提供了一个全新的 Common Lisp 29 | 开发环境,可以支持增量、交互式的开发风格——这是 Lisp 编程所特有的。SLIME 30 | 环境提供了一个完全统一的用户接口,跟你所选择的操作系统和 Common 31 | Lisp 实现无关。为了能有一个具体的开发环境来进行讲解,我将使用这个 32 | Lisp-in-a-Box 环境。而那些想要其他开发环境的人,无论是使用某些商业 33 | Lisp 供应商的图形化集成开发环境(IDE)还是基于其他编辑器的环境,在应用本书的内容时应该都不会有较大的困难。 34 | -------------------------------------------------------------------------------- /docsrc/chap03/a-simple-database.md: -------------------------------------------------------------------------------- 1 | # Practical: A Simple Database(实践:一个简单的数据库) 2 | 3 | Obviously, before you can start building real software in Lisp, you'll 4 | have to learn the language. But let's face it--you may be thinking, 5 | "'Practical Common Lisp,' isn't that an oxymoron? Why should you be 6 | expected to bother learning all the details of a language unless it's 7 | actually good for something you care about?" So I'll start by giving 8 | you a small example of what you can do with Common Lisp. In this 9 | chapter you'll write a simple database for keeping track of 10 | CDs. You'll use similar techniques in Chapter 27 when you build a 11 | database of MP3s for our streaming MP3 server. In fact, you could 12 | think of this as part of the MP3 software project--after all, in order 13 | to have a bunch of MP3s to listen to, it might be helpful to be able 14 | to keep track of which CDs you have and which ones you need to rip. 15 | 16 | 很明显,在你可以用 Lisp 17 | 构建真实软件之前,必须先学会这门语言。但是请想想看——你可能会觉得:“‘实用 18 | Common Lisp 19 | 编程’ 难道不是反语吗?难道在确定一门语言真正有用之前就要先把它所有的细节都学完吗?” 20 | 因此我先给你一个小型的可以用 Common Lisp 来做的例子。本章里编写一个简单的数据库用来记录 21 | CD 光盘。在第 27 章里,为我们的流式 MP3 服务器构建一个 MP3 22 | 数据库还会用到类似的技术。事实上,它可以被看成是整个 MP3 23 | 软件项目的一部分——毕竟,为了有大量的 MP3 可听,对我们所拥有并需要转换成 24 | MP3 的 CD 加以记录是很有用的。 25 | 26 | In this chapter, I'll cover just enough Lisp as we go along for you to 27 | understand how the code works. But I'll gloss over quite a few 28 | details. For now you needn't sweat the small stuff--the next several 29 | chapters will cover all the Common Lisp constructs used here, and 30 | more, in a much more systematic way. 31 | 32 | 在本章,我只介绍足以使你理解代码工作原理的那些 33 | Lisp 特性,但细节方面不会解释太多。目前你不需要执著于细节——接下来的几章将以一种更加系统化的方式介绍这里用到的所有 34 | Common Lisp 控制结构以及更多内容。 35 | 36 | One terminology note: I'll discuss a handful of Lisp operators in this 37 | chapter. In Chapter 4, you'll learn that Common Lisp provides three 38 | distinct kinds of operators: functions, macros, and special 39 | operators. For the purposes of this chapter, you don't really need to 40 | know the difference. I will, however, refer to different operators as 41 | functions or macros or special operators as appropriate, rather than 42 | trying to hide the details behind the word operator. For now you can 43 | treat function, macro, and special operator as all more or less 44 | equivalent. 45 | 46 | 关于术语方面,本章将讨论少量 Lisp 操作符。第4章将学到 Common Lisp 47 | 所提供的三种不同类型的操作符:函数、宏,以及特殊操作符。对于本章来说,你并不需要知道它们的区别。尽管如此,在提及操作符时我还是会适时地说成是函数、宏或特殊操作符,而不会笼统地用“操作符”这个词来表示。眼下你差不多可以认为函数、宏和特殊操作符是等价的。 48 | 49 | Also, keep in mind that I won't bust out all the most sophisticated 50 | Common Lisp techniques for your very first post-"hello, world" 51 | program. The point of this chapter isn't that this is how you would 52 | write a database in Lisp; rather, the point is for you to get an idea 53 | of what programming in Lisp is like and to see how even a relatively 54 | simple Lisp program can be quite featureful. 55 | 56 | 另外请记住我不会在这个继 “hello, world” 后写的首个程序中亮出所有最专业的 57 | Common Lisp 技术来。本章的重点和意图也不在于讲解如何用 Lisp 58 | 编写数据库,而在于让你对 Lisp 编程有个大致的印象,并能看到即便相对简单的 59 | Lisp 程序也可以有着丰富的功能。 60 | -------------------------------------------------------------------------------- /docsrc/chap03/chap03.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docsrc/chap03/filling-cds.md: -------------------------------------------------------------------------------- 1 | # Filling CDs(录入 CD) 2 | 3 | A single record, however, does not a database make. You need some 4 | larger construct to hold the records. Again, for simplicity's sake, a 5 | list seems like a good choice. Also for simplicity you can use a 6 | global variable, `*db*`, which you can define with the **DEFVAR** 7 | macro. The asterisks (`*`) in the name are a Lisp naming convention for 8 | global variables. 9 | 10 | 只有单一记录还不能算是一个数据库,需要一些更大的结构来保存记录。出于简化目的,使用列表似乎也还不错。同样出于简化目的,也可以使用一个全局变量 11 | `*db*`,它可以用 **DEFVAR** 宏来定义。名字中的星号是 Lisp 的全局变量命名约定。 12 | 13 | 14 | ```lisp 15 | (defvar *db* nil) 16 | ``` 17 | 18 | You can use the **PUSH** macro to add items to *db*. But it's probably 19 | a good idea to abstract things a tiny bit, so you should define a 20 | function add-record that adds a record to the database. 21 | 22 | 可以使用 **PUSH** 宏为 `*db*` 添加新的项。但稍微做得抽象一些可能更好,因此可以定义一个函数 23 | `add-record` 来给数据库增加一条记录。 24 | 25 | ```lisp 26 | (defun add-record (cd) (push cd *db*)) 27 | ``` 28 | 29 | Now you can use `add-record` and `make-cd` together to add CDs to the database. 30 | 31 | 现在可以将 `add-record` 和 `make-cd` 一起使用,来为数据库添加新的 CD 记录了。 32 | 33 | ```lisp 34 | CL-USER> (add-record (make-cd "Roses" "Kathy Mattea" 7 t)) 35 | ((:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) 36 | CL-USER> (add-record (make-cd "Fly" "Dixie Chicks" 8 t)) 37 | ((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) 38 | (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) 39 | CL-USER> (add-record (make-cd "Home" "Dixie Chicks" 9 t)) 40 | ((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T) 41 | (:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) 42 | (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)) 43 | ``` 44 | 45 | The stuff printed by the REPL after each call to `add-record` is the 46 | return value, which is the value returned by the last expression in 47 | the function body, the **PUSH**. And **PUSH** returns the new value of the 48 | variable it's modifying. So what you're actually seeing is the value 49 | of the database after the record has been added. 50 | 51 | 那些每次调用 `add-record` 以后 REPL 52 | 所打印出来的东西是返回值,也就是函数体中最后一个表达式 **PUSH** 53 | 所返回的值,并且 **PUSH** 54 | 返回它正在修改的变量的新值。因此你看到的其实是每次新记录被添加以后整个数据库的值。 55 | -------------------------------------------------------------------------------- /docsrc/chap03/wrapping-up.md: -------------------------------------------------------------------------------- 1 | # Wrapping Up(总结) 2 | 3 | Now, an interesting thing has happened. You removed duplication and 4 | made the code more efficient _and_ more general at the same time. That's 5 | often the way it goes with a well-chosen macro. This makes sense 6 | because a macro is just another mechanism for creating 7 | abstractions--abstraction at the syntactic level, and abstractions are 8 | by definition more concise ways of expressing underlying 9 | generalities. Now the only code in the mini-database that's specific 10 | to CDs and the fields in them is in the `make-cd`, `prompt-for-cd`, and 11 | `add-cd` functions. In fact, our new `where` macro would work with any 12 | plist-based database. 13 | 14 | 现在,有趣的事情发生了。你不但去除了重复,而且还使得代码更有效且更通用了。这通常就是正确选用宏所达到的效果。这件事合乎逻辑,是因为宏只不过是另一种创建抽象的手法——词法层面的抽象,以及按照定义通过更简明地表达底层一般性的方式所得到的抽象。现在这个微型数据库的代码中只有 15 | `make-cd`、`prompt-for-cd` 以及 `add-cd` 16 | 函数是特定于 CD 及其字段的。事实上,新的 `where` 宏可以用在任何基于 17 | plist 的数据库上。 18 | 19 | However, this is still far from being a complete database. You can 20 | probably think of plenty of features to add, such as supporting 21 | multiple tables or more elaborate queries. In Chapter 27 we'll build 22 | an MP3 database that incorporates some of those features. 23 | 24 | 尽管如此,它距离一个完整的数据库仍很遥远。你可能会想到还有大量需要增加的特性,包括支持多表或是更复杂的查询。第 25 | 27 章将建立一个具备这些特性的 MP3 数据库。 26 | 27 | The point of this chapter was to give you a quick introduction to just 28 | a handful of Lisp's features and show how they're used to write code 29 | that's a bit more interesting than "hello, world." In the next chapter 30 | we'll begin a more systematic overview of Lisp. 31 | 32 | 本章的要点在于快速介绍少量 Lisp 特性,展示如何用它们编写出比 “hello, 33 | world” 更有趣一点儿的代码。在下一章里,我们将对 Lisp 做一个更加系统的概述。 34 | -------------------------------------------------------------------------------- /docsrc/chap04/chap04.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docsrc/chap04/function-calls.md: -------------------------------------------------------------------------------- 1 | # Function Calls(函数调用) 2 | 3 | The evaluation rule for function call forms is simple: evaluate the 4 | remaining elements of the list as Lisp forms and pass the resulting 5 | values to the named function. This rule obviously places some 6 | additional syntactic constraints on a function call form: all the 7 | elements of the list after the first must themselves be well-formed 8 | Lisp forms. In other words, the basic syntax of a function call form 9 | is as follows, where each of the arguments is itself a Lisp form: 10 | 11 | 函数调用形式的求值规则很简单:对列表的其余元素作为 Lisp 12 | 形式进行求值并将结果传递到命名函数中。这一规则明显的有着一些附加的句法限制在函数调用形式上:除第一个以外,所有的列表元素它们自身必须是一个形态良好的 13 | Lisp 形式。换句话说,函数调用形式的基本语法应如下所示,其中每个参数其本身也是一个 14 | Lisp 形式: 15 | 16 | ```lisp 17 | (function-name argument*) 18 | ``` 19 | 20 | Thus, the following expression is evaluated by first evaluating 1, 21 | then evaluating 2, and then passing the resulting values to the `+` 22 | function, which returns 3: 23 | 24 | 这样,下面这个表达式在求值时将首先求值 1,再求值 2,然后将得到的值传给 25 | `+` 函数,再返回 3: 26 | 27 | ```lisp 28 | (+ 1 2) 29 | ``` 30 | 31 | A more complex expression such as the following is evaluated in 32 | similar fashion except that evaluating the arguments `(+ 1 2)` and 33 | `(- 3 4)` entails first evaluating their arguments and applying the 34 | appropriate functions to them: 35 | 36 | 像下面这样更复杂的表达式也采用相似的求值方法,不过在求值参数 `(+ 1 2)` 37 | 和 `(- 3 4)` 时需要先对它们的参数求值,然后再对它们应用相应的函数: 38 | 39 | ```lisp 40 | (* (+ 1 2) (- 3 4)) 41 | ``` 42 | 43 | Eventually, the values 3 and -1 are passed to the `*` function, which 44 | returns -3. 45 | 46 | 最后,值 3 和 -1 被传递到 `*` 函数里,从而得到 -3。 47 | 48 | As these examples show, functions are used for many of the things that 49 | require special syntax in other languages. This helps keep Lisp's 50 | syntax regular. 51 | 52 | 正如这些例子所显示的这样,许多其他语言中需要特殊语法来处理的事务在 Lisp 53 | 中都可用函数来处理。Lisp 的这种设计对于保持其语法正则化很有帮助。 54 | -------------------------------------------------------------------------------- /docsrc/chap04/macros.md: -------------------------------------------------------------------------------- 1 | # Macros(宏) 2 | 3 | While special operators extend the syntax of Common Lisp beyond what 4 | can be expressed with just function calls, the set of special 5 | operators is fixed by the language standard. Macros, on the other 6 | hand, give users of the language a way to extend its syntax. As you 7 | saw in Chapter 3, a macro is a function that takes s-expressions as 8 | arguments and returns a Lisp form that's then evaluated in place of 9 | the macro form. The evaluation of a macro form proceeds in two phases: 10 | First, the elements of the macro form are passed, unevaluated, to the 11 | macro function. Second, the form returned by the macro 12 | function--called its _expansion_--is evaluated according to the normal 13 | evaluation rules. 14 | 15 | 虽然特殊操作符以超越了函数调用所能表达的方式扩展了 Common Lisp 16 | 语法,但特殊操作符的数量在语言标准中是固定的。然而,宏却能提供给语言用户一种语法扩展方式。如同在第 17 | 3 章里看到的那样,宏是一个以 S-表达式为其参数的函数,并返回一个 18 | Lisp 形式,然后对其求值并用该值取代宏形式。宏形式的求值过程包括两个阶段:首先,宏形式的元素不经求值却被传递到宏函数里;其次,由宏函数所返回的形式(称为其_展开式_)按照正常的求值规则进行求值。 19 | 20 | It's important to keep the two phases of evaluating a macro form clear 21 | in your mind. It's easy to lose track when you're typing expressions 22 | at the REPL because the two phases happen one after another and the 23 | value of the second phase is immediately returned. But when Lisp code 24 | is compiled, the two phases happen at completely different times, so 25 | it's important to keep clear what's happening when. For instance, when 26 | you compile a whole file of source code with the function 27 | **COMPILE-FILE**, all the macro forms in the file are recursively expanded 28 | until the code consists of nothing but function call forms and special 29 | forms. This macroless code is then compiled into a FASL file that the 30 | **LOAD** function knows how to load. The compiled code, however, isn't 31 | executed until the file is loaded. Because macros generate their 32 | expansion at compile time, they can do relatively large amounts of 33 | work generating their expansion without having to pay for it when the 34 | file is loaded or the functions defined in the file are called. 35 | 36 | 重点在于要清醒地认识到一个宏形式求值的两个阶段。当你在 REPL 37 | 中输入表达式时很容易忘记这一点,因为两个阶段相继发生并且后一阶段的值被立即返回了。但是当 38 | Lisp 代码被编译时,这两个阶段所发生时间却是完全不同的,因此关键在于对何时发生什么要保持清醒。例如,当使用函数 39 | **COMPILE-FILE** 来编译整个源代码文件时,文件中所有宏形式将被递归展开,直到代码中只含有函数调用形式和特殊形式。这些无宏的代码随后被编译成一个 40 | FASL文件——**LOAD** 41 | 函数知道如何去加载它。但编译后的代码直到文件被加载时才会被执行。因为宏在编译期会生成其展开式,它们可以用相对大量的工作来生成其展开式,而无需在文件被加载时或是当文件中定义的函数被调用时再付出额外的代价。 42 | 43 | Since the evaluator doesn't evaluate the elements of the macro form 44 | before passing them to the macro function, they don't need to be 45 | well-formed Lisp forms. Each macro assigns a meaning to the 46 | s-expressions in the macro form by virtue of how it uses them to 47 | generate its expansion. In other words, each macro defines its own 48 | local syntax. For instance, the `backwards` macro from Chapter 3 defines 49 | a syntax in which an expression is a legal `backwards` form if it's a 50 | list that's the reverse of a legal Lisp form. 51 | 52 | 由于求值器在将宏形式传递给宏函数之前并不对它们求值,因此它们不需要是格式良好的 53 | Lisp 形式。每个宏都为其宏形式中的符号表达式指定了一种含义,用以指明宏将如何使用它们生成展开式。换句话说,每个宏都定义了它们自己的局部语法。例如,第 3 章的 `backwards` 宏就定义了一种语法,合法的 54 | `backwards` 形式列表必须与合法的 Lisp 形式列表反序。 55 | 56 | I'll talk quite a bit more about macros throughout this book. For now 57 | the important thing for you to realize is that macros--while 58 | syntactically similar to function calls--serve quite a different 59 | purpose, providing a hook into the compiler. 60 | 61 | 我会在本书中经常地提到宏。眼下最为重要的是认识到宏:虽然跟函数调用在句法上相似,但却有着用于相当不同的用途,并提供了一种嵌入编译器的钩子。 62 | -------------------------------------------------------------------------------- /docsrc/chap04/syntax-and-semantics.md: -------------------------------------------------------------------------------- 1 | # Syntax and Semantics(语法和语义) 2 | 3 | After that whirlwind tour, we'll settle down for a few chapters to 4 | take a more systematic look at the features you've used so far. I'll 5 | start with an overview of the basic elements of Lisp's syntax and 6 | semantics, which means, of course, that I must first address that 7 | burning question... 8 | 9 | 在一阵旋风似的介绍之后,我们将坐下来用几章的篇幅对已经用到的那些特性来一次更加系统化的介绍。我将从对 10 | Lisp 的基本语法和语义元素的概述开始,这意味着我必须先回答下面问题…… 11 | -------------------------------------------------------------------------------- /docsrc/chap04/whats-with-all.md: -------------------------------------------------------------------------------- 1 | # What's with All the Parentheses?(括号里都可以有什么) 2 | 3 | Lisp's syntax is quite a bit different from the syntax of languages 4 | descended from Algol. The two most immediately obvious characteristics 5 | are the extensive use of parentheses and prefix notation. For whatever 6 | reason, a lot of folks are put off by this syntax. Lisp's detractors 7 | tend to describe the syntax as "weird" and "annoying." Lisp, they say, 8 | must stand for Lots of Irritating Superfluous Parentheses. Lisp folks, 9 | on the other hand, tend to consider Lisp's syntax one of its great 10 | virtues. How is it that what's so off-putting to one group is a source 11 | of delight to another? 12 | 13 | Lisp 的语法和源自 Algol 14 | 的语言在语法上有很多不同。两者特征最明显的区别在于前者大量使用了括号和前缀表示法。说来也怪,大量的追随者都喜欢这样的语法。Lisp 15 | 的反对者们总是将这种语法描述成 “奇怪的” 和 “讨厌的”,他们说 Lisp 16 | 就是 “大量不合理的多余括号”(Lots of Irritating Superfluous 17 | Parentheses)的简称;而 Lisp 的追随者则认为,Lisp 18 | 的语法是它的最大优势。为什么两个团体之间会有如此对立的见解呢? 19 | 20 | I can't really make the complete case for Lisp's syntax until I've 21 | explained Lisp's macros a bit more thoroughly, but I can start with an 22 | historical tidbit that suggests it may be worth keeping an open mind: 23 | when John McCarthy first invented Lisp, he intended to implement a 24 | more Algol-like syntax, which he called M-expressions. However, he 25 | never got around to it. He explained why not in his article "History 26 | of Lisp." 27 | 28 | 在本章不可能完整地描述 Lisp 的语法,因为我还没有彻底地解释 Lisp 29 | 的宏,但我可以从一些宝贵的历史经验来说明保持开放的思想是值得的:John McCarthy 30 | 首次发明 Lisp 时,曾想过实现一种类 Algol 的语法,他称之为 31 | M-表达式。尽管如此,他却从未实现这一点。他在自己的文章《History of Lisp》中对此加以解释。 32 | 33 | > The project of defining M-expressions precisely and compiling them 34 | > or at least translating them into S-expressions was neither 35 | > finalized nor explicitly abandoned. It just receded into the 36 | > indefinite future, and a new generation of programmers appeared who 37 | > preferred S-expressions to any FORTRAN-like or ALGOL-like notation 38 | > that could be devised. 39 | 40 | > 这个精确定义 M-表达式以及将其编译或至少转译成 41 | > S-表达式的工程,既没有完成也没有明确放弃,它只不过是被无限期地推迟了。(相比 42 | > S-表达式)更加偏爱类 FORTRAN 或类 Algol 表示法的程序员新一代也许会最终实现它。 43 | 44 | In other words, the people who have actually used Lisp over the past 45 | 45 years have liked the syntax and have found that it makes the 46 | language more powerful. In the next few chapters, you'll begin to see 47 | why. 48 | 49 | 换句话说,过去 45 年以来,实际使用 Lisp 50 | 的人们已经喜欢上了这种语法,并且发现它能使该语言变得更为强大。接下来的几章里将告诉你为什么会这样。 51 | -------------------------------------------------------------------------------- /docsrc/chap05/anonymous-functions.md: -------------------------------------------------------------------------------- 1 | # Anonymous Functions(匿名函数) 2 | 3 | Once you start writing, or even simply using, functions that accept 4 | other functions as arguments, you're bound to discover that sometimes 5 | it's annoying to have to define and name a whole separate function 6 | that's used in only one place, especially when you never call it by 7 | name. 8 | 9 | 一旦开始编写或只是使用那些可以接受其他函数作为实参的函数,你就必然发现,有时不得不去定义和命名一个仅使用一次的函数是(尤其是你可能从不用名字来调用它时),这会让人相当恼火。 10 | 11 | When it seems like overkill to define a new function with **DEFUN**, you 12 | can create an "anonymous" function using a **LAMBDA** expression. As 13 | discussed in Chapter 3, a **LAMBDA** expression looks like this: 14 | 15 | 觉得没必要用 **DEFUN** 来定义一个新函数时,可以使用一个 **LAMBDA** 16 | 表达式来创建匿名的函数。第 3 章里讨论过,一个 **LAMBDA** 表达式形式如下: 17 | 18 | ```lisp 19 | (lambda (parameters) body) 20 | ``` 21 | 22 | One way to think of **LAMBDA** expressions is as a special kind of 23 | function name where the name itself directly describes what the 24 | function does. This explains why you can use a **LAMBDA** expression in 25 | the place of a function name with `#'`. 26 | 27 | 可以将 **LAMBDA** 28 | 表达式视为一种特殊类型的函数名,其名字本身直接描述函数的用途。这就解释了为什么可以使用一个带有 29 | `#'` 的 **LAMBDA** 表达式来代替一个函数名。 30 | 31 | ```lisp 32 | (funcall #'(lambda (x y) (+ x y)) 2 3) ==> 5 33 | ``` 34 | 35 | You can even use a **LAMBDA** expression as the "name" of a function in a 36 | function call expression. If you wanted, you could write the previous 37 | **FUNCALL** expression more concisely. 38 | 39 | 甚至还可以在一个函数调用表达式中将 **LAMBDA** 40 | 表达式用作函数名。由此一来,我们也可以在需要时以更简洁方式来书写前面的 41 | **FUNCALL** 表达式如下: 42 | 43 | ```lisp 44 | ((lambda (x y) (+ x y)) 2 3) ==> 5 45 | ``` 46 | 47 | But this is almost never done; it's merely worth noting that it's 48 | legal in order to emphasize that **LAMBDA** expressions can be used 49 | anywhere a normal function name can be. 50 | 51 | 但几乎没人这样做。它唯一的用途是来强调将 **LAMBDA** 52 | 表达式用在任何一个正常函数名可以出现的场合都是合法的。 53 | 54 | Anonymous functions can be useful when you need to pass a function as 55 | an argument to another function and the function you need to pass is 56 | simple enough to express inline. For instance, suppose you wanted to 57 | plot the function `2x`. You could define the following function: 58 | 59 | 在需要传递一个作为参数的函数给另一个函数,并且需要传递的这个函数简单到可以内联表达时,匿名函数特别有用。例如,假设想要绘制函数 `2x`,你可以定义下面的函数: 60 | 61 | ```lisp 62 | (defun double (x) (* 2 x)) 63 | ``` 64 | 65 | which you could then pass to `plot`: 66 | 67 | 并随后将其传给 `plot`: 68 | 69 | ```lisp 70 | CL-USER> (plot #'double 0 10 1) 71 | 72 | ** 73 | **** 74 | ****** 75 | ******** 76 | ********** 77 | ************ 78 | ************** 79 | **************** 80 | ****************** 81 | ******************** 82 | NIL 83 | ``` 84 | 85 | But it's easier, and arguably clearer, to write this: 86 | 87 | 但如果写成这样将会更简单和清晰: 88 | 89 | ```lisp 90 | CL-USER> (plot #'(lambda (x) (* 2 x)) 0 10 1) 91 | 92 | ** 93 | **** 94 | ****** 95 | ******** 96 | ********** 97 | ************ 98 | ************** 99 | **************** 100 | ****************** 101 | ******************** 102 | NIL 103 | ``` 104 | 105 | The other important use of **LAMBDA** expressions is in making closures, 106 | functions that capture part of the environment where they're 107 | created. You used closures a bit in Chapter 3, but the details of how 108 | closures work and what they're used for is really more about how 109 | variables work than functions, so I'll save that discussion for the 110 | next chapter. 111 | 112 | **LAMBDA** 表达式的另一项重要用途是制作闭包(closure),即捕捉了其创建时环境信息的函数。你在第 3 113 | 章里使用了一点儿闭包,但要深入了解闭包的工作原理及其用途,更多的还是要从变量而非函数的角度去考察,因此我将在下一章里讨论它们。 114 | -------------------------------------------------------------------------------- /docsrc/chap05/chap05.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docsrc/chap05/function-parameter-lists.md: -------------------------------------------------------------------------------- 1 | # Function Parameter Lists(函数形参列表) 2 | 3 | There's not a lot more to say about function names or documentation 4 | strings, and it will take a good portion of the rest of this book to 5 | describe all the things you can do in the body of a function, which 6 | leaves us with the parameter list. 7 | 8 | 关于函数名或文档字符串就没有更多可说的了,而本书其余部分将用很多篇幅来描述所有可在一个函数体里做的事情,因此就只需讨论形参列表了。 9 | 10 | The basic purpose of a parameter list is, of course, to declare the 11 | variables that will receive the arguments passed to the function. When 12 | a parameter list is a simple list of variable names--as in 13 | `verbose-sum`--the parameters are called _required parameters_. When a 14 | function is called, it must be supplied with one argument for every 15 | required parameter. Each parameter is bound to the corresponding 16 | argument. If a function is called with too few or too many arguments, 17 | Lisp will signal an error. 18 | 19 | 很明显,一个形参列表的基本用途是为了声明一些变量,用来接收传递给函数的实参。当形参列表是一个由变量名所组成的简单列表时,如同在 20 | `verbose-sum` 里那样,这些形参被称为必要形参。当函数被调用时,必须为它的每一个必要形参都提供一个实参。每一个形参被绑定到对应的实参上。如果一 21 | 个函数以过少或过多的实参来调用的话,Lisp 就会报错。 22 | 23 | However, Common Lisp's parameter lists also give you more flexible 24 | ways of mapping the arguments in a function call to the function's 25 | parameters. In addition to required parameters, a function can have 26 | optional parameters. Or a function can have a single parameter that's 27 | bound to a list containing any extra arguments. And, finally, 28 | arguments can be mapped to parameters using keywords rather than 29 | position. Thus, Common Lisp's parameter lists provide a convenient 30 | solution to several common coding problems. 31 | 32 | 但是,Common Lisp 33 | 的形参列表也给了你更灵活的方式将函数调用实参映射到函数形参。除了必要形参以外,一个函数还可以有可选形参,或是也可以用单一形参绑定到含有任意多个额外参数的列表上。最后,参数还可以通过关键字而不是位置来映射到形参上。这样,Common 34 | Lisp 的形参列表对于几种常见的编码问题提供了一种便利的解决方案。 35 | -------------------------------------------------------------------------------- /docsrc/chap05/function-return-values.md: -------------------------------------------------------------------------------- 1 | # Function Return Values(函数返回值) 2 | 3 | All the functions you've written so far have used the default behavior 4 | of returning the value of the last expression evaluated as their own 5 | return value. This is the most common way to return a value from a 6 | function. 7 | 8 | 目前写出的所有函数都使用了默认的返回值行为,即最后一个表达式的值被作为整个函数的返回值。这是从函数中返回值的最常见方式。 9 | 10 | However, sometimes it's convenient to be able to return from the 11 | middle of a function such as when you want to break out of nested 12 | control constructs. In such cases you can use the **RETURN-FROM** special 13 | operator to immediately return any value from the function. 14 | 15 | 但某些时候,尤其是想要从嵌套的控制结构中脱身时,如果有办法从函数中间返回。那将是非常便利的。在这种情况下,你可以使用 16 | **RETURN-FROM** 特别操作符,它能够立即以任何值从函数中间返回。 17 | 18 | You'll see in Chapter 20 that **RETURN-FROM** is actually not tied to 19 | functions at all; it's used to return from a block of code defined 20 | with the **BLOCK** special operator. However, **DEFUN** automatically wraps 21 | the whole function body in a block with the same name as the 22 | function. So, evaluating a **RETURN-FROM** with the name of the function 23 | and the value you want to return will cause the function to 24 | immediately exit with that value. **RETURN-FROM** is a special operator 25 | whose first "argument" is the name of the block from which to 26 | return. This name isn't evaluated and thus isn't quoted. 27 | 28 | 在第 20 章将会看到,**RETURN-FROM** 29 | 事实上不只用于函数,它还可以被用来从一个由 **BLOCK** 30 | 特别操作符所定义的代码块中返回。不过 **DEFUN** 31 | 会自动将其整个函数体包装在一个与其函数同名的代码块中。因此,对一个带有当前函数名和想要返回的值的 32 | **RETURN-FROM** 进行求值将导致函数立即以该值退出。**RETURN-FROM** 33 | 是一个特殊操作符,其第一个 “参数” 是它想要返回的代码块名。该名字不被求值,因此无需引用。 34 | 35 | The following function uses nested loops to find the first pair of 36 | numbers, each less than 10, whose product is greater than the 37 | argument, and it uses **RETURN-FROM** to return the pair as soon as it 38 | finds it: 39 | 40 | 下面这个函数使用了嵌套循环来发现第一个数对——每个都小于 41 | 10,并且其乘积大于函数的参数,它使用 **RETURN-FROM** 在发现之后立即返回该数对: 42 | 43 | ```lisp 44 | (defun foo (n) 45 | (dotimes (i 10) 46 | (dotimes (j 10) 47 | (when (> (* i j) n) 48 | (return-from foo (list i j)))))) 49 | ``` 50 | 51 | Admittedly, having to specify the name of the function you're 52 | returning from is a bit of a pain--for one thing, if you change the 53 | function's name, you'll need to change the name used in the 54 | **RETURN-FROM** as well. But it's also the case that explicit **RETURN-FROM**s 55 | are used much less frequently in Lisp than return statements in 56 | C-derived languages, because all Lisp expressions, including control 57 | constructs such as loops and conditionals, evaluate to a value. So 58 | it's not much of a problem in practice. 59 | 60 | 必须承认的是,不得不指定正在返回的函数名多少会有些不便——比如改变了函数的名字,就需要同时改变 61 | **RETURN-FROM** 中所使用的名字。 但在事实上,显式的 62 | **RETURN-FROM** 调用在 Lisp 中出现的频率远小于 `return` 63 | 语句在源自 C 的语言里所出现的频率,因为所有的 Lisp 64 | 表达式,包括诸如循环和条件语句这样的控制结构,都会在求值后得到一个返回值。因此在实践中这不是什么问题。 65 | -------------------------------------------------------------------------------- /docsrc/chap05/functions.md: -------------------------------------------------------------------------------- 1 | # Functions(函数) 2 | 3 | After the rules of syntax and semantics, the three most basic 4 | components of all Lisp programs are functions, variables and 5 | macros. You used all three while building the database in Chapter 3, 6 | but I glossed over a lot of the details of how they work and how to 7 | best use them. I'll devote the next few chapters to these three 8 | topics, starting with functions, which--like their counterparts in 9 | other languages--provide the basic mechanism for abstracting, well, 10 | functionality. 11 | 12 | 有了语法和语义规则以后,所有 Lisp 13 | 程序的三个最基本组成部分就是函数、变量和宏。在第 3 14 | 章里构建数据库时已经全部用到了这三个组件,但是我没有详细提及它们是如何工作的以及如何更好使用它们细节。接下来的几章将专门讲解这三个主题,先从函数开始——就像在其他语言里那样,函数提供了用于抽象和功能化的基本方法。 15 | 16 | The bulk of Lisp itself consists of functions. More than three 17 | quarters of the names defined in the language standard name 18 | functions. All the built-in data types are defined purely in terms of 19 | what functions operate on them. Even Lisp's powerful object system is 20 | built upon a conceptual extension to functions, generic functions, 21 | which I'll cover in Chapter 16. 22 | 23 | Lisp 24 | 本身是由大量函数组成的。其语言标准中有超过四分之三的名字用于定义函数。所有内置的数据类型纯粹是用操作它们的函数来定义的。甚至连 25 | Lisp 强大的对象系统也是构建在函数的概念性扩展——广义函数(generic 26 | function)之上的,第 16 章将会介绍它们。 27 | 28 | And, despite the importance of macros to The Lisp Way, in the end all 29 | real functionality is provided by functions. Macros run at compile 30 | time, so the code they generate--the code that will actually make up 31 | the program after all the macros are expanded--will consist entirely 32 | of calls to functions and special operators. Not to mention, macros 33 | themselves are also functions, albeit functions that are used to 34 | generate code rather than to perform the actions of the program. 35 | 36 | 并且,尽管宏对于 Lisp 37 | 风格有着重要的作用,但最终所有实际的功能还是由函数来提供的。宏运行在编译期,因此它们生成的代码,即当所有宏被展开后将实际构成程序的那些代码,将完全由对函数和特殊操作符的调用所构成。更不用说,宏本身也是函数了——尽管这种函数是用来生成代码,而不是用来完成实际的程序操作的。 38 | -------------------------------------------------------------------------------- /docsrc/chap05/rest-parameters.md: -------------------------------------------------------------------------------- 1 | # Rest Parameters(剩余形参) 2 | 3 | Optional parameters are just the thing when you have discrete 4 | parameters for which the caller may or may not want to provide 5 | values. But some functions need to take a variable number of 6 | arguments. Several of the built-in functions you've seen already work 7 | this way. **FORMAT** has two required arguments, the stream and the 8 | control string. But after that it needs a variable number of arguments 9 | depending on how many values need to be interpolated into the control 10 | string. The `+` function also takes a variable number of 11 | arguments--there's no particular reason to limit it to summing just 12 | two numbers; it will sum any number of values. (It even works with 13 | zero arguments, returning 0, the identity under addition.) The 14 | following are all legal calls of those two functions: 15 | 16 | 可选形参仅适用于一些较为分散并且不能确定调用者是否会供值的形参。但某些函数需要接收可变数量的实参,比如说前文已然出现过的一些内置函数。**FORMAT** 17 | 有两个必要实参,即流和控制串。但在这两个之后,它还需要一组可变数量的实参,这取决于控制串需要插入多少个值。`+` 18 | 函数也接受可变数量的实参——没有特别的理由限制它只能在两个数之间相加,它对任意数量的值做加法运算(它甚至可以没有实参,此时返回 19 | 0——加法的底数)。下面这些都是这两个函数的合法调用: 20 | 21 | ```lisp 22 | (format t "hello, world") 23 | (format t "hello, ~a" name) 24 | (format t "x: ~d y: ~d" x y) 25 | (+) 26 | (+ 1) 27 | (+ 1 2) 28 | (+ 1 2 3) 29 | ``` 30 | 31 | Obviously, you could write functions taking a variable number of 32 | arguments by simply giving them a lot of optional parameters. But that 33 | would be incredibly painful--just writing the parameter list would be 34 | bad enough, and that doesn't get into dealing with all the parameters 35 | in the body of the function. To do it properly, you'd have to have as 36 | many optional parameters as the number of arguments that can legally 37 | be passed in a function call. This number is implementation dependent 38 | but guaranteed to be at least 50. And in current implementations it 39 | ranges from 4,096 to 536,870,911. Blech. That kind of mind-bending 40 | tedium is definitely not The Lisp Way. 41 | 42 | 很明显,也可以通过简单地给它一些可选形参来写出接受可变数量实参的函数,但这样将会非常麻烦——光是写形参列表就已经足够麻烦了,何况还要在函数体中处理所有这些形参。为了做好这件事,还将不得不使用一个合法的函数调用所能够传递的那么多的可选形参。这一具体数量与具体实现相关,但可以保证至少有 43 | 50 个。在当前所有实现中,它的最大值范围从 4096 到 44 | 536,870,911。这种绞尽脑汁的无聊事情绝对不是 Lisp 风格。 45 | 46 | Instead, Lisp lets you include a catchall parameter after the symbol 47 | `&rest`. If a function includes a `&rest` parameter, any arguments 48 | remaining after values have been doled out to all the required and 49 | optional parameters are gathered up into a list that becomes the value 50 | of the `&rest` parameter. Thus, the parameter lists for **FORMAT** and `+` 51 | probably look something like this: 52 | 53 | 相反,Lisp 允许在符号 `&rest` 之后包括一揽子形参。如果函数带有 `&rest` 54 | 形参,那么任何满足了必要和可选形参之后的其余所有实参就将被收集到一个列表里成为该 55 | `&rest` 形参的值。这样,**FORMAT** 和 `+` 的形参列表可能看起来会是这样: 56 | 57 | ```lisp 58 | (defun format (stream string &rest values) ...) 59 | (defun + (&rest numbers) ...) 60 | ``` 61 | -------------------------------------------------------------------------------- /docsrc/chap06/assignment.md: -------------------------------------------------------------------------------- 1 | # Assignment(赋值) 2 | 3 | Once you've created a binding, you can do two things with it: get the 4 | current value and set it to a new value. As you saw in Chapter 4, a 5 | symbol evaluates to the value of the variable it names, so you can get 6 | the current value simply by referring to the variable. To assign a new 7 | value to a binding, you use the **SETF** macro, Common Lisp's 8 | general-purpose assignment operator. The basic form of **SETF** is as 9 | follows: 10 | 11 | 一旦创建了绑定,就可以对它做两件事:获取当前值以及为它设置新值。正如在第 4 12 | 章里所看到的,一个符号求值到它所命名的变量的值,因此,可以简单地通过引用这个变量来得到它的当前值。而为绑定赋予新值则要使用 13 | **SETF** 宏——Common 14 | Lisp 的通用赋值操作符。下面是 **SETF** 的基本形式: 15 | 16 | ```lisp 17 | (setf place value) 18 | ``` 19 | 20 | Because **SETF** is a macro, it can examine the form of the place it's 21 | assigning to and expand into appropriate lower-level operations to 22 | manipulate that `place`. When the place is a variable, it expands into a 23 | call to the special operator SETQ, which, as a special operator, has 24 | access to both lexical and dynamic bindings.15 For instance, to assign 25 | the value 10 to the variable x, you can write this: 26 | 27 | 因为 **SETF** 是宏,所以它可以检查它所赋值的 `place` 28 | 上的形式,并展开成适当的底层操作来修改那个位置,当该位置是变量时,它展开成一个对特殊操作符 29 | **SETQ** 的调用,后者可以访问到词法和动态绑定。 例如,为了将值 10 30 | 赋给变量 `x`,可以写成这样: 31 | 32 | ```lisp 33 | (setf x 10) 34 | ``` 35 | 36 | As I discussed earlier, assigning a new value to a binding has no 37 | effect on any other bindings of that variable. And it doesn't have any 38 | effect on the value that was stored in the binding prior to the 39 | assignment. Thus, the **SETF** in this function: 40 | 41 | 正如早先所讨论的,为一个绑定赋予新值对该变量的任何其他绑定没有效果。并且它对赋值之前绑定上所保存的值也没有任何效果。因此,函数 42 | 43 | ```lisp 44 | (defun foo (x) (setf x 10)) 45 | ``` 46 | 47 | will have no effect on any value outside of `foo`. The binding that was 48 | created when `foo` was called is set to 10, immediately replacing 49 | whatever value was passed as an argument. In particular, a form such 50 | as the following: 51 | 52 | 中的 **SETF** 对于 `foo` 53 | 之外的任何值都没有效果,这个当 `foo` 被调用时所创建的绑定被设置到 54 | 10,立即替换了作为参数传递的任何值。特别是在如下形式中。 55 | 56 | ```lisp 57 | (let ((y 20)) 58 | (foo y) 59 | (print y)) 60 | ``` 61 | 62 | will print 20, not 10, as it's the value of y that's passed to foo 63 | where it's briefly the value of the variable x before the **SETF** gives x 64 | a new value. 65 | 66 | 将打印出 20 而不是 10,因为传递给 `foo` 的 `y` 67 | 的值在该函数中变成了 `x` 的值,随后又被 **SETF** 设置成新值。 68 | 69 | **SETF** can also assign to multiple places in sequence. For instance, 70 | instead of the following: 71 | 72 | **SETF** 也可用于依次对多个位置赋值。例如,与其像下面这样: 73 | 74 | ```lisp 75 | (setf x 1) 76 | (setf y 2) 77 | ``` 78 | 79 | you can write this: 80 | 81 | 也可以写成这样: 82 | 83 | ```lisp 84 | (setf x 1 y 2) 85 | ``` 86 | 87 | **SETF** returns the newly assigned value, so you can also nest calls to 88 | **SETF** as in the following expression, which assigns both x and y the 89 | same random value: 90 | 91 | **SETF** 返回最近被赋予的值,因此也可以像下面的表达式那样嵌套调用 92 | **SETF**,将 `x` 和 `y` 赋予同一个随机值: 93 | 94 | ```lisp 95 | (setf x (setf y (random 10))) 96 | ``` 97 | -------------------------------------------------------------------------------- /docsrc/chap06/chap06.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docsrc/chap06/constants.md: -------------------------------------------------------------------------------- 1 | # Constants(常量) 2 | 3 | One other kind of variable I haven't mentioned at all is the 4 | oxymoronic "constant variable." All constants are global and are 5 | defined with **DEFCONSTANT**. The basic form of **DEFCONSTANT** 6 | is like **DEFPARAMETER**. 7 | 8 | 我尚未提到的另一种类型的变量是所谓的 9 | “常值变量”。所有的常量都是全局的,并且使用 **DEFCONSTANT** 10 | 的定义,**DEFCONSTANT** 的基本形式与 **DEFPARAMETER** 相似。 11 | 12 | ```lisp 13 | (defconstant name initial-value-form [ documentation-string ]) 14 | ``` 15 | 16 | As with **DEFVAR** and **DEFPARAMETER**, **DEFCONSTANT** has a global effect on 17 | the name used--thereafter the name can be used only to refer to the 18 | constant; it can't be used as a function parameter or rebound with any 19 | other binding form. Thus, many Lisp programmers follow a naming 20 | convention of using names starting and ending with `+` for 21 | constants. This convention is somewhat less universally followed than 22 | the `*`-naming convention for globally special names but is a good idea 23 | for the same reason. 24 | 25 | 与 **DEFVAR** 和 **DEFPARAMETER** 相似,**DEFCONSTANT** 26 | 在其使用的名字上产生了一个全局效果——从此该名字仅被用于指向常量;它不能被用作函数形参或是用任何其他的绑定形式进行重绑定。因此,许多 27 | Lisp 程序员遵循了一个命名约定,用以 `+` 28 | 开始或结尾的名字来表示常量,这一约定在某种程度上不像全局特殊名字的 `*` 29 | 命名约定那样流行,但同样也不错。 30 | 31 | Another thing to note about **DEFCONSTANT** is that while the language 32 | allows you to redefine a constant by reevaluating a **DEFCONSTANT** with a 33 | different `initial-value-form`, what exactly happens after the 34 | redefinition isn't defined. In practice, most implementations will 35 | require you to reevaluate any code that refers to the constant in 36 | order to see the new value since the old value may well have been 37 | inlined. Consequently, it's a good idea to use **DEFCONSTANT** only to 38 | define things that are really constant, such as the value of NIL. For 39 | things you might ever want to change, you should use **DEFPARAMETER** 40 | instead. 41 | 42 | 关于 **DEFCONSTANT**,需要注意的另一点是,尽管语言允许通过重新求值一个带有一个初始值形式的 43 | **DEFCONSTANT** 44 | 来重定义一个常量,但在重定义之后究竟发生什么是没有定义的。在实践上,多数实现将要求任何对引用了该常量的代码进行求值以便它们能看到新值,因为老的值可能已经被内联到代码中了。因此最好只用 45 | **DEFCONSTANT** 46 | 来定义那些真正是常量的东西,例如π的值。而对于那些可能想改变的东西,则应转而使用 47 | **DEFPARAMETER**。 48 | -------------------------------------------------------------------------------- /docsrc/chap06/variables.md: -------------------------------------------------------------------------------- 1 | # Variables(变量) 2 | 3 | The next basic building block we need to look at are variables. Common 4 | Lisp supports two kinds of variables: lexical and dynamic.1 These two 5 | types correspond roughly to "local" and "global" variables in other 6 | languages. However, the correspondence is only approximate. On one 7 | hand, some languages' "local" variables are in fact much like Common 8 | Lisp's dynamic variables.2 And on the other, some languages' local 9 | variables are lexically scoped without providing all the capabilities 10 | provided by Common Lisp's lexical variables. In particular, not all 11 | languages that provide lexically scoped variables support closures. 12 | 13 | 我们需要了解的下一个基本程序构造单元是变量。Common Lisp 14 | 支持两种类型的变量:词法(lexical)变量和动态(dynamic)变量。这两种变 15 | 量类型分别对应于其他语言中的局部变量和全局变量,不过也只能说是大致相似。一方面,某些语言中的局部变量更像是 16 | Common Lisp 17 | 的动态变量 。另一方面,某些语言中的局部变量虽然是词法作用域的,但却并没有提供由 18 | Common Lisp 19 | 的词法变量所提供的所有功能。尤其是并非所有语言都提供了支持闭包的词法作用域变量。 20 | 21 | To make matters a bit more confusing, many of the forms that deal with 22 | variables can be used with both lexical and dynamic variables. So I'll 23 | start by discussing a few aspects of Lisp's variables that apply to 24 | both kinds and then cover the specific characteristics of lexical and 25 | dynamic variables. Then I'll discuss Common Lisp's general-purpose 26 | assignment operator, SETF, which is used to assign new values to 27 | variables and just about every other place that can hold a value. 28 | 29 | 许多含有变量的表达式都可以同时使用词法和动态变量,这样一来更令人困惑了。因此本章我先讨论同时涉及到两种类型的 30 | Lisp 变量的几个方面,然后再谈及词法变量和动态变量各自的特征。随后再讨论 31 | Common Lisp 32 | 的通用赋值操作符 **SETF**,它用于为变量和其他任何可以保存值的地方赋予新值。 33 | -------------------------------------------------------------------------------- /docsrc/chap07/and-or-and-not.md: -------------------------------------------------------------------------------- 1 | # AND, OR, and NOT(AND、OR 和 NOT) 2 | 3 | When writing the conditions in **IF**, **WHEN**, **UNLESS**, and **COND** forms, three 4 | operators that will come in handy are the boolean logic operators, 5 | **AND**, **OR**, and **NOT**. 6 | 7 | 在使用 **IF**、**WHEN**、**UNLESS** 和 **COND** 8 | 形式编写条件语句时,经常用到的三个操作符是布尔逻辑操作符 **AND**、**OR** 9 | 和 **NOT**。 10 | 11 | **NOT** is a function so strictly speaking doesn't belong in this chapter, 12 | but it's closely tied to **AND** and **OR**. It takes a single argument and 13 | inverts its truth value, returning **T** if the argument is **NIL** and **NIL** 14 | otherwise. 15 | 16 | 严格来讲,**NOT** 这个函数并不属于本章讨论范畴,但它跟 **AND** 和 **OR** 17 | 紧密相关。它接受单一参数并对其真值取反,当参数为 **NIL** 时返回 **T**,否则返回 **NIL**。 18 | 19 | AND and OR, however, are macros. They implement logical conjunction 20 | and disjunction of any number of subforms and are defined as macros so 21 | they can short-circuit. That is, they evaluate only as many of their 22 | subforms--in left-to-right order--as necessary to determine the 23 | overall truth value. Thus, AND stops and returns NIL as soon as one of 24 | its subforms evaluates to NIL. If all the subforms evaluate to 25 | non-NIL, it returns the value of the last subform. OR, on the other 26 | hand, stops as soon as one of its subforms evaluates to non-NIL and 27 | returns the resulting value. If none of the subforms evaluate to true, 28 | OR returns NIL. Here are some examples: 29 | 30 | 而 **AND** 和 **OR** 31 | 则是宏。它们实现了对任意数量子表达式的逻辑合取和析取操作,并被定义成宏以便支持 32 | “短路” 特性。也就是说,它们仅以从左到右的顺序对用于检测整体真值的必要数量的子表达式进行求值。这样,只要 33 | **AND** 的一个子表达式求值为 **NIL**,它就立即停止并返回 34 | **NIL**。如果所有子表达式都求值到非 **NIL**,那么它将返回最后一个子表达式的值。而对于 35 | **OR** 来说只要一个子表达式求值到非 36 | **NIL**,它就立即停止并返回当前子表达式的值。如果没有子表达式求值到真,**OR** 37 | 返回 **NIL**。下面是一些例子: 38 | 39 | ```lisp 40 | (not nil) ==> T 41 | (not (= 1 1)) ==> NIL 42 | (and (= 1 2) (= 3 3)) ==> NIL 43 | (or (= 1 2) (= 3 3)) ==> T 44 | ``` 45 | -------------------------------------------------------------------------------- /docsrc/chap07/chap07.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docsrc/chap07/cond.md: -------------------------------------------------------------------------------- 1 | # COND(COND 宏) 2 | 3 | Another time raw **IF** expressions can get ugly is when you have a 4 | multibranch conditional: if `a` do x, else if `b` do y; else do z. There's 5 | no logical problem writing such a chain of conditional expressions 6 | with just **IF**, but it's not pretty. 7 | 8 | 当遇到多重分支的条件语句时,原始的 **IF** 9 | 表达式再一次变得丑陋不堪:如果 `a` 成立那么执行 x,否则如果 `b` 10 | 成立那么执行 y;否则执行 z。只用 **IF** 11 | 来写这样的条件表达式链并没有逻辑问题,只是不太好看。 12 | 13 | ```lisp 14 | (if a 15 | (do-x) 16 | (if b 17 | (do-y) 18 | (do-z))) 19 | ``` 20 | 21 | And it would be even worse if you needed to include multiple forms in 22 | the then clauses, requiring **PROGN**s. So, not surprisingly, Common Lisp 23 | provides a macro for expressing multibranch conditionals: **COND**. This 24 | is the basic skeleton: 25 | 26 | 并且如果需要在 then 子句中包括多个形式,那就需要用到 27 | **PROGN**,而那样事情就会变得更糟。因此毫不奇怪地,Common Lisp 28 | 提供了一个用于表达多重分支条件的宏 **COND**。下面是它的基本结构: 29 | 30 | ```lisp 31 | (cond 32 | (test-1 form*) 33 | . 34 | . 35 | . 36 | (test-N form*)) 37 | ``` 38 | 39 | Each element of the body represents one branch of the conditional and 40 | consists of a list containing a condition form and zero or more forms 41 | to be evaluated if that branch is chosen. The conditions are evaluated 42 | in the order the branches appear in the body until one of them 43 | evaluates to true. At that point, the remaining forms in that branch 44 | are evaluated, and the value of the last form in the branch is 45 | returned as the value of the **COND** as a whole. If the branch contains 46 | no forms after the condition, the value of the condition is returned 47 | instead. By convention, the branch representing the final else clause 48 | in an if/else-if chain is written with a condition of **T**. Any non-**NIL** 49 | value will work, but a **T** serves as a useful landmark when reading the 50 | code. Thus, you can write the previous nested IF expression using **COND** 51 | like this: 52 | 53 | 主体中的每个元素都代表一个条件分支,并由一个列表所构成,列表中含有一个条件形式,以及零或多个当该分支被选择时将被求值的形式。这些条件形式按照分支在主体中出现的顺序被依次求值,直到它们中的一个求值为真。这时,该分支中的其余形式将被求值,且分支中最后一个形式的值将被作为整个 54 | **COND** 的返回值。如果该分支在条件形式之后不再含有其他形式,那么就将 55 | 返回该条件形式的值。习惯上,那个用来表示 56 | if/else-if 链中最后一个 else 57 | 子句的分支将被写成带有条件 **T**。虽然任何非 **NIL** 58 | 的值都可以使用,但在阅读代码时,**T** 59 | 标记确实有用。这样就可以像下面这样用 **COND** 60 | 来写出前面的嵌套 **IF** 表达式: 61 | 62 | ```lisp 63 | (cond (a (do-x)) 64 | (b (do-y)) 65 | (t (do-z))) 66 | ``` 67 | -------------------------------------------------------------------------------- /docsrc/chap07/dolist-and-dotimes.md: -------------------------------------------------------------------------------- 1 | # DOLIST and DOTIMES(DOLIST 和 DOTIMES 宏) 2 | 3 | I'll start with the easy-to-use **DOLIST** and **DOTIMES** macros. 4 | 5 | 先从易于使用的 **DOLIST** 和 **DOTIMES** 宏开始。 6 | 7 | **DOLIST** loops across the items of a list, executing the loop body with 8 | a variable holding the successive items of the list. This is the 9 | basic skeleton (leaving out some of the more esoteric options): 10 | 11 | **DOLIST** 12 | 在一个列表的元素上循环操作,使用一个依次持有列表中所有后继元素的变量来执行循环体。下面是其基本形式(去掉了一些比较难懂的选项): 13 | 14 | ```lisp 15 | (dolist (var list-form) 16 | body-form*) 17 | ``` 18 | 19 | When the loop starts, the `list-form` is evaluated once to produce a 20 | list. Then the body of the loop is evaluated once for each item in 21 | the list with the variable `var` holding the value of the item. For 22 | instance: 23 | 24 | 当循环开始时,`list-form` 25 | 被求值一次以产生一个列表。然后循环体在列表的每一项上求值一次,同时用变量 26 | `var` 保存当前项的值。例如: 27 | 28 | ```lisp 29 | CL-USER> (dolist (x '(1 2 3)) (print x)) 30 | 1 31 | 2 32 | 3 33 | NIL 34 | ``` 35 | 36 | Used this way, the **DOLIST** form as a whole evaluates to **NIL**. 37 | 38 | 在这种方式下,**DOLIST** 这种形式本身求值为 **NIL**。 39 | 40 | If you want to break out of a **DOLIST** loop before the end of the list, 41 | you can use **RETURN**. 42 | 43 | 如果想在列表结束之前中断一个 **DOLIST** 循环,则可以使用 **RETURN**。 44 | 45 | ```lisp 46 | CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return))) 47 | 1 48 | 2 49 | NIL 50 | ``` 51 | 52 | **DOTIMES** is the high-level looping construct for counting loops. The 53 | basic template is much the same as **DOLIST**'s. 54 | 55 | **DOTIMES** 是用于循环计数的高级循环构造。其基本模板和 **DOLIST** 非常相似。 56 | 57 | ```lisp 58 | (dotimes (var count-form) 59 | body-form*) 60 | ``` 61 | 62 | The `count-form` must evaluate to an integer. Each time through the loop 63 | `var` holds successive integers from 0 to one less than that number. For 64 | instance: 65 | 66 | 其中的 `count-form` 必须要能求值为一个整数。通过每次循环,`var` 67 | 所持有的整数依次为从 0 到比那个数小 1 的每一个后继整数。例如: 68 | 69 | ```lisp 70 | CL-USER> (dotimes (i 4) (print i)) 71 | 0 72 | 1 73 | 2 74 | 3 75 | NIL 76 | ``` 77 | 78 | As with **DOLIST**, you can use **RETURN** to break out of the loop early. 79 | 80 | 和 **DOLIST** 一样,也可以使用 **RETURN** 来提前中断循环。 81 | 82 | Because the body of both **DOLIST** and **DOTIMES** loops can contain any kind 83 | of expressions, you can also nest loops. For example, to print out the 84 | times tables from 1 × 1 = 1 to 20 × 20 = 400, you can write this pair 85 | of nested **DOTIMES** loops: 86 | 87 | 由于 **DOLIST** 和 **DOTIMES** 88 | 的循环体中可以包含任何类型的表达式,因此也可以使用嵌套循环。例如,为了打印出从 89 | 1 × 1 = 1 到 20 × 20 = 400 的乘法表,可以写出下面这对嵌套的 90 | **DOTIMES** 循环: 91 | 92 | ```lisp 93 | (dotimes (x 20) 94 | (dotimes (y 20) 95 | (format t "~3d " (* (1+ x) (1+ y)))) 96 | (format t "~%")) 97 | ``` 98 | -------------------------------------------------------------------------------- /docsrc/chap07/looping.md: -------------------------------------------------------------------------------- 1 | # Looping(循环) 2 | 3 | Control constructs are the other main kind of looping 4 | constructs. Common Lisp's looping facilities are--in addition to being 5 | quite powerful and flexible--an interesting lesson in the 6 | have-your-cake-and-eat-it-too style of programming that macros 7 | provide. 8 | 9 | 循环结构是另外一类主要的控制结构。Common Lisp 10 | 的循环机制,除了更加强大和灵活以外,还是一门关于宏所提供的 11 | “鱼和熊掌兼得” 的编程风格的有趣课程。 12 | 13 | As it turns out, none of Lisp's 25 special operators directly support 14 | structured looping. All of Lisp's looping control constructs are 15 | macros built on top of a pair of special operators that provide a 16 | primitive `goto` facility. Like many good abstractions, syntactic or 17 | otherwise, Lisp's looping macros are built as a set of layered 18 | abstractions starting from the base provided by those two special 19 | operators. 20 | 21 | 初看起来,Lisp 的 25 22 | 个特殊操作符中没有一个能够直接支持结构化循环,所有的 Lisp 23 | 循环控制构造都是构建在一对提供原生 `goto` 24 | 机制的特殊操作符之上的宏。和许多好的抽象或句法等一样,Lisp 25 | 的循环宏构建在以那两个特殊操作符为基础的一组分层抽象之上。 26 | 27 | At the bottom (leaving aside the special operators) is a very general 28 | looping construct, **DO**. While very powerful, **DO** suffers, as do many 29 | general-purpose abstractions, from being overkill for simple 30 | situations. So Lisp also provides two other macros, **DOLIST** and 31 | **DOTIMES**, that are less flexible than **DO** but provide convenient support 32 | for the common cases of looping over the elements of a list and 33 | counting loops. While an implementation can implement these macros 34 | however it wants, they're typically implemented as macros that expand 35 | into an equivalent **DO** loop. Thus, **DO** provides a basic structured 36 | looping construct on top of the underlying primitives provided by 37 | Common Lisp's special operators, and **DOLIST** and **DOTIMES** provide two 38 | easier-to-use, if less general, constructs. And, as you'll see in the 39 | next chapter, you can build your own looping constructs on top of **DO** 40 | for situations where **DOLIST** and **DOTIMES** don't meet your needs. 41 | 42 | 最底层(不考虑特殊操作符)是一个非常通用的循环构造 43 | **DO**。尽管非常强大,但 **DO** 44 | 和许多其他的通用抽象一样,在应用于简单情形时显得过于复杂。因此 45 | Lisp 还提供了另外两个宏,**DOLIST** 和 **DOTIMES**。它们不像 46 | **DO** 那样灵活,但却提供了对于常见的在列表元素上循环和计数循环的便利支持。尽管一个实现可以用任何方式来实现这些宏,但它们被典型实现为展开到等价 47 | **DO** 循环的宏。因此,在由 Common Lisp 48 | 特殊操作符所提供的底层原语之上,**DO** 49 | 提供了一种基本的结构化循环构造,而 **DOLIST** 和 **DOTIMES** 50 | 则提供了两种易用却不那么通用的构造。并且如同在下一章将看到的那样,对于那些 51 | **DOLIST** 和 **DOTIMES** 无法满足需要的情形,还可以在 **DO** 52 | 之上构建自定义的循环构造。 53 | 54 | Finally, the **LOOP** macro provides a full-blown mini-language for 55 | expressing looping constructs in a non-Lispy, English-like (or at 56 | least Algol-like) language. Some Lisp hackers love **LOOP**; others hate 57 | it. **LOOP**'s fans like it because it provides a concise way to express 58 | certain commonly needed looping constructs. Its detractors dislike it 59 | because it's not Lispy enough. But whichever side one comes down on, 60 | it's a remarkable example of the power of macros to add new constructs 61 | to the language. 62 | 63 | 最后,**LOOP** 宏提供了一种成熟的微型语言,它用一种非 Lisp 64 | 的类似英语(或至少类似 Algol)的语言来表达循环构造。一些 Lisp 黑客热爱 65 | **LOOP**,其他人则讨厌它。**LOOP** 66 | 爱好者们喜欢它是因为它用了一种简洁的方式来表达特定的常用循环构造。而贬低者们不喜欢它则是因为它不太像 67 | Lisp。但无论你倾向于哪一方,**LOOP** 68 | 本身都是一个为语言增加新构造的宏展示其强大威力的突出示例。 69 | -------------------------------------------------------------------------------- /docsrc/chap08/a-sample-macro.md: -------------------------------------------------------------------------------- 1 | # A Sample Macro: `do-primes`(一个示例宏:`do-primes`) 2 | 3 | To see how this three-step process works, you'll write a macro 4 | `do-primes` that provides a looping construct similar to **DOTIMES** and 5 | **DOLIST** except that instead of iterating over integers or elements of a 6 | list, it iterates over successive prime numbers. This isn't meant to 7 | be an example of a particularly useful macro--it's just a vehicle for 8 | demonstrating the process. 9 | 10 | 为了观察这三步过程是怎样发挥作用的,下面将编写一个叫做 `do-primes` 11 | 的宏,它提供了一个类似 DOTIMES 和 DOLIST 12 | 的循环结构,只是它并非迭代在整数或者一个列表的元素上,而是迭代在相继的素数上。这并非意味着这是一个特别有用的宏,只是为了演示宏的定义过程罢了。 13 | 14 | First, you'll need two utility functions, one to test whether a given 15 | number is prime and another that returns the next prime number greater 16 | or equal to its argument. In both cases you can use a simple, but 17 | inefficient, brute-force approach. 18 | 19 | 首先你需要两个工具函数:一个用来测试给定的数是否为素数,另一个用来返回大于或等于其实参的下一个素数。这两种情况都可以使用简单而低效的暴力手法来解决。 20 | 21 | ```lisp 22 | (defun primep (number) 23 | (when (> number 1) 24 | (loop for fac from 2 to (isqrt number) never (zerop (mod number fac))))) 25 | 26 | (defun next-prime (number) 27 | (loop for n from number when (primep n) return n)) 28 | ``` 29 | 30 | Now you can write the macro. Following the procedure outlined 31 | previously, you need at least one example of a call to the macro and 32 | the code into which it should expand. Suppose you start with the idea 33 | that you want to be able to write this: 34 | 35 | 现在就可以写这个宏了。按照前面所概括的过程,至少需要一个宏调用示例以及它应当展开成的代码。假设开始时想通过如下代码: 36 | 37 | ```lisp 38 | (do-primes (p 0 19) 39 | (format t "~d " p)) 40 | ``` 41 | 42 | to express a loop that executes the body once each for each prime 43 | number greater or equal to 0 and less than or equal to 19, with the 44 | variable p holding the prime number. It makes sense to model this 45 | macro on the form of the standard **DOLIST** and **DOTIMES** macros; macros 46 | that follow the pattern of existing macros are easier to understand 47 | and use than macros that introduce gratuitously novel syntax. 48 | 49 | 来表达一个循环,在每个大于等于 0 并小于等于 19 50 | 的素数上分别依次执行循环体,并以变量 `p` 保存当前素数。仿照标准 51 | **DOLIST** 和 **DOTIMES** 52 | 宏来定义是合理的。按照已有宏的模式操作的宏比那些引入了无谓的新颖语法的宏更易于理解和使用。 53 | 54 | Without the `do-primes` macro, you could write such a loop with **DO** (and 55 | the two utility functions defined previously) like this: 56 | 57 | 如果没有 `do-primes` 宏,你可以用 58 | **DO**(和前面定义的两个工具函数)来写出下面这个循环: 59 | 60 | ```lisp 61 | (do ((p (next-prime 0) (next-prime (1+ p)))) 62 | ((> p 19)) 63 | (format t "~d " p)) 64 | ``` 65 | 66 | Now you're ready to start writing the macro code that will translate 67 | from the former to the latter. 68 | 69 | 现在就可以开始编写将前者转化成后者的代码了。 70 | -------------------------------------------------------------------------------- /docsrc/chap08/beyond-simple-macros.md: -------------------------------------------------------------------------------- 1 | # Beyond Simple Macros(更复杂的宏) 2 | 3 | I could, of course, say a lot more about macros. All the macros you've 4 | seen so far have been fairly simple examples that save you a bit of 5 | typing but don't provide radical new ways of expressing things. In 6 | upcoming chapters you'll see examples of macros that allow you to 7 | express things in ways that would be virtually impossible without 8 | macros. You'll start in the very next chapter, in which you'll build a 9 | simple but effective unit test framework. 10 | 11 | 当然,我可以说更多关于宏的事情。目前为止,所有你见到的宏都是相当简单的例子,它们帮助你减轻了一些写代码的工作量,但却并没有提供表达事物的根本性的新方式。在接下来的章节里你将看到一些宏的示例,它们允许你以一种假如没有宏就完全做不到的方式来表达事物。从下一章开始,你将构建一个简单而高效的单元测试框架。 12 | -------------------------------------------------------------------------------- /docsrc/chap08/chap08.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docsrc/chap08/macros-defining-your-own.md: -------------------------------------------------------------------------------- 1 | # Macros: Defining Your Own(定义你自己的宏) 2 | 3 | Now it's time to start writing your own macros. The standard macros I 4 | covered in the previous chapter hint at some of the things you can do 5 | with macros, but that's just the beginning. Common Lisp doesn't 6 | support macros so every Lisp programmer can create their own variants 7 | of standard control constructs any more than C supports functions so 8 | every C programmer can write trivial variants of the functions in the 9 | C standard library. Macros are part of the language to allow you to 10 | create abstractions on top of the core language and standard library 11 | that move you closer toward being able to directly express the things 12 | you want to express. 13 | 14 | 现在可以开始编写自己的宏了。前一章里提及的标准宏暗示了可以用宏做到的某些事情,但这只是开始。相比 15 | C 语言的函数可以让每个 C 程序员编写 C 标准库中的函数的简单变体而言,Common 16 | Lisp 的宏也无非是可以让每个 Lisp 17 | 程序员可以创建他们自己的标准控制构造变体罢了。作为语言的一部分,宏能够用于在核心语言和标准库之上创建抽象,从而使你更直接地表达想表达的事物。 18 | 19 | Perhaps the biggest barrier to a proper understanding of macros is, 20 | ironically, that they're so well integrated into the language. In many 21 | ways they seem like just a funny kind of function--they're written in 22 | Lisp, they take arguments and return results, and they allow you to 23 | abstract away distracting details. Yet despite these many 24 | similarities, macros operate at a different level than functions and 25 | create a totally different kind of abstraction. 26 | 27 | 具有讽刺意义的是,也许对于宏的正确理解,最大的障碍是它们已经很好地集成到了语言里。在许多方面,它们看起来只是一些有趣的函数——它们用 28 | Lisp 写成,接受参数并返回结果。同时,它们允许你将那些分散注意力的细节抽象掉。尽管有这些相似性,但宏的操作层面却与函数不同,而且它还有着完全不同类型的抽象。 29 | 30 | Once you understand the difference between macros and functions, the 31 | tight integration of macros in the language will be a huge 32 | benefit. But in the meantime, it's a frequent source of confusion for 33 | new Lispers. The following story, while not true in a historical or 34 | technical sense, tries to alleviate the confusion by giving you a way 35 | to think about how macros work. 36 | 37 | 一旦理解了宏与函数之间的区别,你就会发现这门语言中宏的紧密集成所带来的巨大优势。但同时它也是经常导致新程序员困惑的主要原因。下面来讲个故事,尽管从历史或技术意义上来说都并不是真的,然而通过这种方式,你倒是可以思考一下宏的工作方式,以此来缓解一下困惑。 38 | -------------------------------------------------------------------------------- /docsrc/chap08/once-only.md: -------------------------------------------------------------------------------- 1 | # Another classic macro-writing MACRO: `ONCE-ONLY`(另一个经典的用于编写宏的宏:`ONCE-ONLY`) 2 | 3 | Another classic macro-writing macro is `once-only`, which is used to 4 | generate code that evaluates certain macro arguments once only and in 5 | a particular order. Using once-only, you could write `do-primes` almost 6 | as simply as the original leaky version, like this: 7 | 8 | 另一个经典的用于编写宏的宏是 9 | `once-only`,它用来生成以特定顺序仅求值特定宏参数一次的代码。使用 10 | `once-only`,你几乎可以跟最初的有漏洞版本一样简单地写出 `do-primes` 11 | 来,就像这样: 12 | 13 | ```lisp 14 | (defmacro do-primes ((var start end) &body body) 15 | (once-only (start end) 16 | `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))) 17 | ((> ,var ,end)) 18 | ,@body))) 19 | ``` 20 | 21 | However, the implementation of `once-only` is a bit too involved for a 22 | blow-by-blow explanation, as it relies on multiple levels of 23 | backquoting and unquoting. If you really want to sharpen your macro 24 | chops, you can try to figure out how it works. It looks like this: 25 | 26 | 尽管如此,但如果详加解释的话,`once-only` 27 | 的实现将远远超出本章的内容,因为它依赖于多层的反引用和解引用。如果真想进一步提高宏技术的话,你可以尝试分析它的工作方式。如下所示: 28 | 29 | ```lisp 30 | (defmacro once-only ((&rest names) &body body) 31 | (let ((gensyms (loop for n in names collect (gensym)))) 32 | `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 33 | `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 34 | ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 35 | ,@body))))) 36 | ``` 37 | -------------------------------------------------------------------------------- /docsrc/chap09/abstraction-emerges.md: -------------------------------------------------------------------------------- 1 | # An Abstraction Emerges(抽象诞生) 2 | 3 | In fixing the test functions, you've introduced several new bits of 4 | duplication. Not only does each function have to include the name of 5 | the function twice--once as the name in the **DEFUN** and once in the 6 | binding of `*test-name*`--but the same three-line code pattern is 7 | duplicated between the two functions. You could remove the duplication 8 | simply on the grounds that duplication is bad. But if you look more 9 | closely at the root cause of the duplication, you can learn an 10 | important lesson about how to use macros. 11 | 12 | 在修复测试函数的过程中,你又引入了一点儿新的重复。不但每个函数都需要包含其函数名两次——一次作为 13 | **DEFUN** 中的名字,另一次是在 `*test-name*` 14 | 绑定里,而且同样的三行代码模式被重复使用在两个函数中。你可以在认定所有的重复都有害这一思路的指导下继续消除这些重复。但如果更进一步地调查一下导致代码重复的根本原因,你就可以学到关于如何使用宏的重要一课。 15 | 16 | The reason both these functions start the same way is because they're 17 | both test functions. The duplication arises because, at the moment, 18 | test function is only half an abstraction. The abstraction exists in 19 | your mind, but in the code there's no way to express "this is a test 20 | function" other than to write code that follows a particular pattern. 21 | 22 | 这两个函数的定义都以相同的方式开始,原因在于它们都是测试函数。导致重复是因为此时测试函数只做了一半抽象。这种抽象存在于你的头脑中,但在代码里没有办法表达 23 | “这是一个测试函数”,除非按照特定的模式来写代码。 24 | 25 | Unfortunately, partial abstractions are a crummy tool for building 26 | software. Because a half abstraction is expressed in code by a 27 | manifestation of the pattern, you're guaranteed to have massive code 28 | duplication with all the normal bad consequences that implies for 29 | maintainability. More subtly, because the abstraction exists only in 30 | the minds of programmers, there's no mechanism to make sure different 31 | programmers (or even the same programmer working at different times) 32 | actually understand the abstraction the same way. To make a complete 33 | abstraction, you need a way to express "this is a test function" and 34 | have all the code required by the pattern be generated for you. In 35 | other words, you need a macro. 36 | 37 | 不幸的是,部分抽象对于构建软件来说是很糟糕的,因为一个半成品的抽象在代码中就是通过模式来表现的,因此必然会得到大量的重复代码,它们将带有一切影响程序可维护性的不良后果。更糟糕的是,因为这种抽象仅存在于程序员的思路之中,所以实际上无法保证不同的程序员(或者甚至是工作在不同时期的同一个程序员)会以同样的方式来理解这种抽象。为了得到一个完整的抽象,你需要用一种方法来表达 38 | “这是一个测试函数”,并且这种方法要能将模式所需的全部代码都生成出来。换句话说,你需要一个宏。 39 | 40 | Because the pattern you're trying to capture is a **DEFUN** plus some 41 | boilerplate code, you need to write a macro that will expand into a 42 | **DEFUN**. You'll then use this macro, instead of a plain **DEFUN** to define 43 | test functions, so it makes sense to call it deftest. 44 | 45 | 由于试图捕捉的模式是一个 **DEFUN** 46 | 加上一些样板代码,所以需要写一个宏使其展开成 47 | **DEFUN**。然后使用该宏(而不是用一个简单的 48 | **DEFUN**)去定义测试函数,因此可以将其称为 `deftest`。 49 | 50 | ```lisp 51 | (defmacro deftest (name parameters &body body) 52 | `(defun ,name ,parameters 53 | (let ((*test-name* ',name)) 54 | ,@body))) 55 | ``` 56 | 57 | With this macro you can rewrite `test-+` as follows: 58 | 59 | 使用该宏你可以像下面这样重写 `test-+`: 60 | 61 | ```lisp 62 | (deftest test-+ () 63 | (check 64 | (= (+ 1 2) 3) 65 | (= (+ 1 2 3) 6) 66 | (= (+ -1 -3) -4))) 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /docsrc/chap09/building-a-unit-test-framework.md: -------------------------------------------------------------------------------- 1 | # Practical: Building a Unit Test Framework(实践:构建一个单元测试框架) 2 | 3 | In this chapter you'll return to cutting code and develop a simple 4 | unit testing framework for Lisp. This will give you a chance to use 5 | some of the features you've learned about since Chapter 3, including 6 | macros and dynamic variables, in real code. 7 | 8 | 在本章里,你将编写代码为 Lisp 9 | 开发一个简单的单元测试框架。这将使你有机会在真实代码中使用从第 3 10 | 章起已学到的某些语言特性,包括宏和动态变量。 11 | 12 | The main design goal of the test framework will be to make it as easy 13 | as possible to add new tests, to run various suites of tests, and to 14 | track down test failures. For now you'll focus on designing a 15 | framework you can use during interactive development. 16 | 17 | 该测试框架的主要设计目标是使其可以尽可能简单地增加新测试,运行多个测试套件,以及跟踪测试的失败。目前,你将集中于设计一个可以在交互开发期间使用的框架。 18 | 19 | The key feature of an automated testing framework is that the 20 | framework is responsible for telling you whether all the tests 21 | passed. You don't want to spend your time slogging through test output 22 | checking answers when the computer can do it much more quickly and 23 | accurately. Consequently, each test case must be an expression that 24 | yields a boolean value--true or false, pass or fail. For instance, if 25 | you were writing tests for the built-in `+` function, these might be 26 | reasonable test cases: 27 | 28 | 一个自动测试框架的关键特性在于该框架应该能够告诉你是否所有的测试都通过了。当计算机可以处理得更快更精确时,你就不应该将时间花在埋头检查测试所输出的答案上。因此,每个测试用例必须是一个能产生布尔值的表达式——真或假,通过或失败。举个例子,如果正在为内置的 `+` 29 | 函数编写测试,那么下面这些可能是合理的测试用例: 30 | 31 | ```lisp 32 | (= (+ 1 2) 3) 33 | (= (+ 1 2 3) 6) 34 | (= (+ -1 -3) -4) 35 | ``` 36 | 37 | Functions that have side effects will be tested slightly 38 | differently--you'll have to call the function and then check for 39 | evidence of the expected side effects. But in the end, every test case 40 | has to boil down to a boolean expression, thumbs up or thumbs down. 41 | 42 | 带有副作用的函数会以稍微不同的方式进行测试。你必须调用该函数,然后查找是否有证据表明存在着预期的副作用。 但最终,每个测试用例都将归结为一个布尔表达式,要么真要么假。 43 | -------------------------------------------------------------------------------- /docsrc/chap09/chap09.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docsrc/chap09/two-first-tries.md: -------------------------------------------------------------------------------- 1 | # Two First Tries(两个最初的尝试) 2 | 3 | If you were doing ad hoc testing, you could enter these expressions at 4 | the REPL and check that they return **T**. But you want a framework that 5 | makes it easy to organize and run these test cases whenever you 6 | want. If you want to start with the simplest thing that could possibly 7 | work, you can just write a function that evaluates the test cases and 8 | **AND**s the results together. 9 | 10 | 如果正在做探索测试,那么就可以在REPL中输入这些表达式并检查它们是否返回 11 | **T**。但你可能想要一个框架使其可以在需要时轻松地组织和运行这些测试用例。如果想先处理最简单的可行情况,那就可以只写一个函数,让它对所有的测试用例都予以求值并用 12 | **AND** 将结果连在一起 13 | 14 | ```lisp 15 | (defun test-+ () 16 | (and 17 | (= (+ 1 2) 3) 18 | (= (+ 1 2 3) 6) 19 | (= (+ -1 -3) -4))) 20 | ``` 21 | 22 | Whenever you want to run this set of test cases, you can call `test-+`. 23 | 24 | 无论何时,当想要运行这组测试用例时,都可以调用 `test-+`。 25 | 26 | ```lisp 27 | CL-USER> (test-+) 28 | T 29 | ``` 30 | 31 | As long as it returns **T**, you know the test cases are passing. This way 32 | of organizing tests is also pleasantly concise--you don't have to 33 | write a bunch of test bookkeeping code. However, as you'll discover 34 | the first time a test case fails, the result reporting leaves 35 | something to be desired. When `test-+` returns **NIL**, you'll know 36 | something failed, but you'll have no idea which test case it was. 37 | 38 | 一旦它返回 39 | **T**,就可知道测试用例通过了。这种组织测试的方式也很优美简洁——不需要编写大量的重复测试代码。然而一旦发现某个测试用例失败了,同样也会发现它的运行报告会遗漏一些有用的信息。当 40 | `test-+` 返回 **NIL** 41 | 时,你会知道某些测试失败了,但却不会知道这究竟是哪一个测试用例。 42 | 43 | So let's try another simple--even simpleminded--approach. To find out 44 | what happens to each test case, you could write something like this: 45 | 46 | 因此,让我们来尝试另一种简单得甚至有些愚蠢的方法。为了找出每个测试用例的运行情况,你可以写成类似下面这样: 47 | 48 | ```lisp 49 | (defun test-+ () 50 | (format t "~:[FAIL~;pass~] ... ~a~%" (= (+ 1 2) 3) '(= (+ 1 2) 3)) 51 | (format t "~:[FAIL~;pass~] ... ~a~%" (= (+ 1 2 3) 6) '(= (+ 1 2 3) 6)) 52 | (format t "~:[FAIL~;pass~] ... ~a~%" (= (+ -1 -3) -4) '(= (+ -1 -3) -4))) 53 | ``` 54 | 55 | Now each test case will be reported individually. The `~:[FAIL~;pass~]` 56 | part of the **FORMAT** directive causes **FORMAT** to print "FAIL" if the 57 | first format argument is false and "pass" otherwise. Then you label 58 | the result with the test expression itself. Now running `test-+` shows 59 | you exactly what's going on. 60 | 61 | 现在每个测试用例都将单独报告结果。**FORMAT** 指令中的 `~:[FAIL~;pass~]` 62 | 部分将导致FORMAT在其第一个格式实参为假时打印出 “FAIL”,而在其他情况下为 63 | “pass”。 然后会将测试表达式本身标记到结果上。现在运行 `test-+` 64 | 就可以明确显示出发生了什么事。 65 | 66 | ```lisp 67 | CL-USER> (test-+) 68 | pass ... (= (+ 1 2) 3) 69 | pass ... (= (+ 1 2 3) 6) 70 | pass ... (= (+ -1 -3) -4) 71 | NIL 72 | ``` 73 | 74 | This time the result reporting is more like what you want, but the 75 | code itself is pretty gross. The repeated calls to **FORMAT** as well as 76 | the tedious duplication of the test expression cry out to be 77 | refactored. The duplication of the test expression is particularly 78 | grating because if you mistype it, the test results will be 79 | mislabeled. 80 | 81 | 这次的结果报告更像是你想要的,可是代码本身却一团糟。问题出在对 **FORMAT** 82 | 的重复调用以及测试表达式乏味的重复,这些急切需要被重构。其中测试表达式的重复尤其讨厌,因为如果发生了错误输入,测试结果就会被错误地标记。 83 | 84 | Another problem is that you don't get a single indicator whether all 85 | the test cases passed. It's easy enough, with only three test cases, 86 | to scan the output looking for "FAIL"; however, when you have hundreds 87 | of test cases, it'll be more of a hassle. 88 | 89 | 另一个问题在于,你无法得到单一的关于所有测试是否都通过的指示。如果只有三个测试用例的话,很容易通过扫描输出并查找 90 | “FAIL” 来看到这点。不过当有几百个测试用例时,这将非常困难。 91 | -------------------------------------------------------------------------------- /docsrc/chap10/chap10.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docsrc/chap10/character-comparisons.md: -------------------------------------------------------------------------------- 1 | # Character Comparisons(字符比较) 2 | 3 | The main thing you can do with characters, other than putting them 4 | into strings (which I'll get to later in this chapter), is to compare 5 | them with other characters. Since characters aren't numbers, you can't 6 | use the numeric comparison functions, such as `<` and `>`. Instead, two 7 | sets of functions provide character-specific analogs to the numeric 8 | comparators; one set is case-sensitive and the other case-insensitive. 9 | 10 | 可以对字符做的主要操作除了将它们放进字符串(我将在本章后面讨论这点)之外,还可将它们与其他字符相比较。由于字符不是数字,所以不能使用诸如 11 | `<` 和`>` 12 | 这样的数值比较函数。作用替代,有两类函数提供了数值比较符的特定于字符的相似物:一类是大小写相关的,而另一类是大小写无关的。 13 | 14 | The case-sensitive analog to the numeric `=` is the function **CHAR=**. Like 15 | `=`, **CHAR=** can take any number of arguments and returns true only if 16 | they're all the same character. The case- insensitive version is 17 | **CHAR-EQUAL**. 18 | 19 | 数值 `=` 的大小写相关相似物是函数 **CHAR=**。像 20 | `=` 那样,**CHAR=** 21 | 可以接受任意数量的实参并只在它们全是相同字符时才返回真。大小写无关版本是 22 | **CHAR-EQUAL**。 23 | 24 | The rest of the character comparators follow this same naming scheme: 25 | the case-sensitive comparators are named by prepending the analogous 26 | numeric comparator with CHAR; the case-insensitive versions spell out 27 | the comparator name, separated from the **CHAR** with a hyphen. Note, 28 | however, that `<=` and `>=` are "spelled out" with the logical equivalents 29 | **NOT-GREATERP** and **NOT-LESSP** rather than the more verbose 30 | **LESSP-OR-EQUALP** and **GREATERP-OR-EQUALP**. Like their numeric 31 | counterparts, all these functions can take one or more 32 | arguments. Table 10-1 summarizes the relation between the numeric and 33 | character comparison functions. 34 | 35 | 其余的字符比较函数遵循了相同的命名模式:大小写相关的比较符通过在其对应的数值比较符前面加上 36 | **CHAR** 来命名:大小写无关的版本拼出比较符的名字,前面加上 **CHAR** 37 | 和一个连字符。不过,注意 `<=` 和 `>=` 被拼写成其逻辑等价形式 38 | **NOT-GREATERP** 和 **NOT-LESSP**,而不是更确切的 **LESSP-OR-EQUALP** 39 | 和 **GREATERP-OR-EQUALP**。和它们的数值等价物一样,所有这些函数都接受一个或更多参数。表 40 | 10-1 总结了数值和字符比较函数之间的关系。 41 | 42 | > Table 10-1. Character Comparison Functions 43 | 44 | | Numeric Analog | Case-Sensitive | Case-Insensitive | 45 | | :------------- | :------------- | :-------------------- | 46 | | `=` | **CHAR=** | **CHAR-EQUAL** | 47 | | `/=` | **CHAR/=** | **CHAR-NOT-EQUAL** | 48 | | `<` | **CHAR<** | **CHAR-LESSP** | 49 | | `>` | **CHAR>** | **CHAR-GREATERP** | 50 | | `<=` | **CHAR<=** | **CHAR-NOT-GREATERP** | 51 | | `>=` | **CHAR>=** | **CHAR-NOT-LESSP** | 52 | 53 | 54 | Other functions that deal with characters provide functions for, among 55 | other things, testing whether a given character is alphabetic or a 56 | digit character, testing the case of a character, obtaining a 57 | corresponding character in a different case, and translating between 58 | numeric values representing character codes and actual character 59 | objects. Again, for complete details, see your favorite Common Lisp 60 | reference. 61 | 62 | 其他处理字符的函数包括测试一个给定字符是否是字母或者数字字符,测试一个字符的大小写,获取不同大小写的对应字符,以及在代表字符编码的数值和实际字符对象之间转化。对于完整的细节,请参见你喜爱的 63 | Common Lisp 参考。 64 | -------------------------------------------------------------------------------- /docsrc/chap10/characters.md: -------------------------------------------------------------------------------- 1 | # Characters(字符) 2 | 3 | Common Lisp characters are a distinct type of object from 4 | numbers. That's as it should be--characters are not numbers, and 5 | languages that treat them as if they are tend to run into problems 6 | when character encodings change, say, from 8-bit ASCII to 21-bit 7 | Unicode.11 Because the Common Lisp standard didn't mandate a 8 | particular representation for characters, today several Lisp 9 | implementations use Unicode as their "native" character encoding 10 | despite Unicode being only a gleam in a standards body's eye at the 11 | time Common Lisp's own standardization was being wrapped up. 12 | 13 | Common Lisp 14 | 字符和数字是不同类型的对象。其本该如此——字符不是数字,而将其同等对待的语言当字符编码改变时(比如说从 15 | 8 位 ASCII 到 21 位 Unicode)可能会出现问题。由于 Common Lisp 16 | 标准并未规定字符的内部表示方法,当今几种 Lisp 实现都使用 Unicode 17 | 作为其原生字符编码,尽管从标准化组织的观点来看 Unicode 在 Common 18 | Lisp 自身的标准化成型时期只是昙花一现。 19 | 20 | The read syntax for characters objects is simple: `#\` followed by the 21 | desired character. Thus, `#\x` is the character `x`. Any character can be 22 | used after the `#\`, including otherwise special characters such as `"`, 23 | `(`, and whitespace. However, writing whitespace characters this way 24 | isn't very (human) readable; an alternative syntax for certain 25 | characters is `#\` followed by the character's name. Exactly what names 26 | are supported depends on the character set and on the Lisp 27 | implementation, but all implementations support the names `Space` and 28 | `Newline`. Thus, you should write `#\Space` instead of `#\ `, though the 29 | latter is technically legal. Other semistandard names (that 30 | implementations must use if the character set has the appropriate 31 | characters) are `Tab`, `Page`, `Rubout`, `Linefeed`, `Return`, and `Backspace`. 32 | 33 | 字符的读取语法很简单:`#\` 后跟想要的字符。这样,`#\x` 就是字符 34 | `x`。任何字符都可以用在 `#\` 之后,包括那些诸如 `"`、`(` 35 | 和空格这样的特殊字符。但以这种方式来写空格字符却并不是十分的人类可读,特定字符的替代语法是 36 | `#\` 后跟该字符的名字。具体支持的名字取决于字符集和所在的 37 | Lisp 实现,但所有实现都支持名字 `Space` 和 38 | `Newline`。这样就应该写成用 `#\Space` 来代替 39 | `#\`,尽管后者在技术上是合法的。其他半标准化的名字(如果字符集包含相应的字符实现就必须采用的名字)是 40 | `Tab`、`Page`、`Rubout`、`Linefeed`、`Return` 和 `Backspace`。 41 | -------------------------------------------------------------------------------- /docsrc/chap10/higher-math.md: -------------------------------------------------------------------------------- 1 | # Higher Math(高等数学) 2 | 3 | The functions you've seen so far are the beginning of the built-in 4 | mathematical functions. Lisp also supports logarithms: **LOG**; 5 | exponentiation: **EXP** and **EXPT**; the basic trigonometric functions: **SIN**, 6 | **COS**, and **TAN**; their inverses: **ASIN**, **ACOS**, and 7 | **ATAN**; hyperbolic 8 | functions: **SINH**, **COSH**, and **TANH**; and their inverses: **ASINH**, **ACOSH**, and 9 | **ATANH**. It also provides functions to get at the individual bits of an 10 | integer and to extract the parts of a ratio or a complex number. For a 11 | complete list, see any Common Lisp reference. 12 | 13 | 目前为止,你所看到的函数只是初级的内置数学函数。Lisp 14 | 也支持对数函数:**LOG**,指数函数:**EXP**、**EXPT**,基本三角函数:**SIN**、**COS** 15 | 和 **TAN** 及其它们的逆:**ASIN**、**ACOS** 和 16 | **ATAN**,双曲函数:**SINH**、**COSH** 和 **TANH** 17 | 以及它们的逆:**ASINH**、**ACOSH** 和 18 | **ATANH**。它还提供了函数用来获取一个整数中单独的位以及取出一个比值或一个复数中的部分。完整的函数列表参见任何 19 | Common Lisp 参考。 20 | -------------------------------------------------------------------------------- /docsrc/chap10/numbers-characters-strings.md: -------------------------------------------------------------------------------- 1 | # Numbers, Characters, and Strings(数字、字符和字符串) 2 | 3 | While functions, variables, macros, and 25 special operators provide 4 | the basic building blocks of the language itself, the building blocks 5 | of your programs will be the data structures you use. As Fred Brooks 6 | observed in *The Mythical Man-Month*, "Representation is the essence 7 | of programming." 8 | 9 | 尽管函数、变量、宏和 10 | 25 个特殊操作符组成了语言本身的基本构造单元,你的程序的基本构造单元将是你所使用的数据结构。正如 11 | Fred Brooks 在 *The Mythical Man-Month* 12 | 里提到的,“编程的本质在于表示。” 13 | 14 | Common Lisp provides built-in support for most of the data types 15 | typically found in modern languages: numbers (integer, floating point, 16 | and complex), characters, strings, arrays (including multidimensional 17 | arrays), lists, hash tables, input and output streams, and an 18 | abstraction for portably representing filenames. Functions are also a 19 | first-class data type in Lisp--they can be stored in variables, passed 20 | as arguments, returned as return values, and created at runtime. 21 | 22 | Common Lisp 23 | 为现代语言中常见的大多数数据类型都提供了内置支持:数字(整数、浮点数和复数)、字符 、字符串、数组(包括多维数组)、列表、哈希表、输入和输出流以及一种可移植地表示文件名的抽象。函数在 24 | Lisp 25 | 中也是第一类(fist-class)数据类型。它们可以被保存在变量中,可以作为实参传递,也可以作为返回值返回以及在运行期创建。 26 | 27 | And these built-in types are just the beginning. They're defined in 28 | the language standard so programmers can count on them being available 29 | and because they tend to be easier to implement efficiently when 30 | tightly integrated with the rest of the implementation. But, as you'll 31 | see in later chapters, Common Lisp also provides several ways for you 32 | to define new data types, define operations on them, and integrate 33 | them with the built-in data types. 34 | 35 | 而这些内置类型仅仅是开始。它们被定义在语言标准中,因此程序员们可以依赖于它们的存在,并且也因为它们可以跟语言的其余部分紧密集成,从而使其可以更容易地高效实现。但正如你将在后续章节里看到的那样,Common 36 | Lisp 另外还提供了几种定义新的数据类型的方式,并能定义对其的操作,并能将它们与内置数据类型集成起来。 37 | 38 | For now, however, you can start with the built-in data types. Because 39 | Lisp is a high-level language, the details of exactly how different 40 | data types are implemented are largely hidden. From your point of view 41 | as a user of the language, the built-in data types are defined by the 42 | functions that operate on them. So to learn a data type, you just have 43 | to learn about the functions you can use with it. Additionally, most 44 | of the built-in data types have a special syntax that the Lisp reader 45 | understands and that the Lisp printer uses. That's why, for instance, 46 | you can write strings as `"foo"`; numbers as `123`, `1/23`, and `1.23`; and 47 | lists as `(a b c)`. I'll describe the syntax for different kinds of 48 | objects when I describe the functions for manipulating them. 49 | 50 | 但目前将先从内置数据类型开始讲起。因为 Lisp 51 | 是一种高级语言,不同的数据类型的具体实现细节在很大程度上是隐藏的。从语言用户的角度来看,内置数据类型是由操作它们的函数所定义的。因此为了学习一个数据类型,你只需学会那些与之一起使用的函数就行了。另外,多数内置数据类型都具有 52 | Lisp 读取器所理解并且 Lisp 53 | 打印器可使用的特殊语法。所以你才能将字符串写成 54 | `"foo"`,将数字写成 `123`、`1/23` 和 `1.23`,以及把列表写成 55 | `(a b c)`。我将在描述操作它们的函数时具体描述不同对象的语法。 56 | 57 | In this chapter, I'll cover the built-in "scalar" data types: numbers, 58 | characters, and strings. Technically, strings aren't true scalars--a 59 | string is a sequence of characters, and you can access individual 60 | characters and manipulate strings with a function that operates on 61 | sequences. But I'll discuss strings here because most of the 62 | string-specific functions manipulate them as single values and also 63 | because of the close relation between several of the string functions 64 | and their character counterparts. 65 | 66 | 本章将介绍内置的 “标量” 67 | 数据类型:数字、字符和字符串。从技术上来讲,字符串并不是真正的标量。字符串是字符的序列,你可以访问单独的字符并使用一个操作在序列上的函数来处理该字符串。但我在这里讨论字符串则是因为多数字符串的相关函数会将它们作为单一值来处理,同时也是因为某些字符串函数与它们的字符组成部分之间有着紧密的关系。 68 | 69 | -------------------------------------------------------------------------------- /docsrc/chap10/numbers.md: -------------------------------------------------------------------------------- 1 | # Numbers(数值) 2 | 3 | Math, as Barbie says, is hard. Common Lisp can't make the math part 4 | any easier, but it does tend to get in the way a lot less than other 5 | programming languages. That's not surprising given its mathematical 6 | heritage. Lisp was originally designed by a mathematician as a tool 7 | for studying mathematical functions. And one of the main projects of 8 | the MAC project at MIT was the Macsyma symbolic algebra system, 9 | written in Maclisp, one of Common Lisp's immediate 10 | predecessors. Additionally, Lisp has been used as a teaching language 11 | at places such as MIT where even the computer science professors 12 | cringe at the thought of telling their students that 10/4 = 2, leading 13 | to Lisp's support for exact ratios. And at various times Lisp has been 14 | called upon to compete with FORTRAN in the high-performance numeric 15 | computing arena. 16 | 17 | 正如 Barbie 所说,数学很难。虽说 Common Lisp 18 | 并不能使其数学部分变得简单一些,但它确实可以比其它编程语言在这方面简单不少,考虑到它的数学传统这并不奇怪。Lisp 19 | 最初是用作研究数学函数的工具由一位数学家设计而成的。并且 20 | MIT 的 MAC 项目的主要成果——Macsyma 符号代数系统也是由 21 | Maclisp(一种 Common Lisp 的前身)写成的。此外,Lisp 还曾被用作 MIT 22 | 这类院校的教学语言,在那里即便连计算机学教授们也不愿意告诉学生 10/4=2,从而使 23 | Lisp 能够支持精确比值。Lisp 还曾经多次在高性能数值计算领域与 FORTRAN 竞争。 24 | 25 | One of the reasons Lisp is a nice language for math is its numbers 26 | behave more like true mathematical numbers than the approximations of 27 | numbers that are easy to implement in finite computer hardware. For 28 | instance, integers in Common Lisp can be almost arbitrarily large 29 | rather than being limited by the size of a machine word.3 And dividing 30 | two integers results in an exact ratio, not a truncated value. And 31 | since ratios are represented as pairs of arbitrarily sized integers, 32 | ratios can represent arbitrarily precise fractions. 33 | 34 | Lisp 35 | 是一门用于数学的良好语言,其原因之一是它的数字更加接近于真正的数学数字而不是易于在有穷计算机硬件上实现的近似数字。例如,Common Lisp 36 | 中的整数可以几乎是任意大而不是被限制在一个机器字的大小上。而两个整数相除将得到一个确切的比值而非截断的值。并且比值是由成对的任意大小的整数表示的,所以比值可以表示任意精度的分数。 37 | 38 | On the other hand, for high-performance numeric programming, you may 39 | be willing to trade the exactitude of rationals for the speed offered 40 | by using the hardware's underlying floating-point operations. So, 41 | Common Lisp also offers several types of floating-point numbers, which 42 | are mapped by the implementation to the appropriate hardware-supported 43 | floating-point representations.5 Floats are also used to represent the 44 | results of a computation whose true mathematical value would be an 45 | irrational number. 46 | 47 | 另一方面,对于高性能数值编程,你可能想要用有理数的精度来换取使用硬件的底层浮点操作所得到的速度。因此 48 | Common Lisp 49 | 也提供了几种浮点数,它们可以映射到适当的硬件支持浮点表达的实现上。浮点数也被用于表示其真正数学值为无理数的计算结果。 50 | 51 | Finally, Common Lisp supports complex numbers--the numbers that result 52 | from doing things such as taking square roots and logarithms of 53 | negative numbers. The Common Lisp standard even goes so far as to 54 | specify the principal values and branch cuts for irrational and 55 | transcendental functions on the complex domain. 56 | 57 | 最后,Common Lisp 支持复数——通过在负数上获取平方根和对数所得到的结果。Common 58 | Lisp 标准甚至还指定了复域上无理和超越函数的主值和分支截断。 59 | -------------------------------------------------------------------------------- /docsrc/chap10/numeric-comparisons.md: -------------------------------------------------------------------------------- 1 | # Numeric Comparisons(数值比较) 2 | 3 | The function `=` is the numeric equality predicate. It compares numbers 4 | by mathematical value, ignoring differences in type. Thus, `=` will 5 | consider mathematically equivalent values of different types 6 | equivalent while the generic equality predicate **EQL** would consider 7 | them inequivalent because of the difference in type. (The generic 8 | equality predicate **EQUALP**, however, uses = to compare numbers.) If 9 | it's called with more than two arguments, it returns true only if they 10 | all have the same value. Thus: 11 | 12 | 函数 `=` 13 | 是数值等价谓词。它用数学意义上的值来比较数字,而忽略类型上的区别。这样,`=` 14 | 将把不同类型在数学意义上等价的值视为等价,而通用等价谓词 **EQL** 15 | 将由于其类型差异而视其不等价。(但通用等价谓词 **EQUALP** 16 | 使用 `=` 来比较数字。)如果它以超过两个参数被调用,它将只有当所有参数具有相同值时才返回真。如下所示: 17 | 18 | ```lisp 19 | (= 1 1) ==> T 20 | (= 10 20/2) ==> T 21 | (= 1 1.0 #c(1.0 0.0) #c(1 0)) ==> T 22 | ``` 23 | 24 | The `/=` function, conversely, returns true only if all its arguments 25 | are different values. 26 | 27 | 相反,函数 `/=` 只有当它的全部参数都是不同值才返回真。 28 | 29 | ```lisp 30 | (/= 1 1) ==> NIL 31 | (/= 1 2) ==> T 32 | (/= 1 2 3) ==> T 33 | (/= 1 2 3 1) ==> NIL 34 | (/= 1 2 3 1.0) ==> NIL 35 | ``` 36 | 37 | The functions `<`, `>`, `<=`, and `>=` order rationals and floating-point 38 | numbers (in other words, the real numbers.) Like `=` and `/=`, these 39 | functions can be called with more than two arguments, in which case 40 | each argument is compared to the argument to its right. 41 | 42 | 函数 `<`、`>`、`<=` 和 `>=` 检查有理数和浮点数(也就是实数)的次序。跟 `=` 43 | 和 `/=` 相似,这些函数也可以用超过两个参数来调用,这时每个参数都跟其右边的那个参数相比较。 44 | 45 | ```lisp 46 | (< 2 3) ==> T 47 | (> 2 3) ==> NIL 48 | (> 3 2) ==> T 49 | (< 2 3 4) ==> T 50 | (< 2 3 3) ==> NIL 51 | (<= 2 3 3) ==> T 52 | (<= 2 3 3 4) ==> T 53 | (<= 2 3 4 3) ==> NIL 54 | ``` 55 | 56 | To pick out the smallest or largest of several numbers, you can use 57 | the function **MIN** or **MAX**, which takes any number of real number 58 | arguments and returns the minimum or maximum value. 59 | 60 | 要想选出几个数字中最小或最大的那个,你可以使用函数 **MIN** 或 61 | **MAX**,其接受任意数量的实数参数并返回最小或最大值。 62 | 63 | ```lisp 64 | (max 10 11) ==> 11 65 | (min -12 -10) ==> -12 66 | (max -1 2 -3) ==> 2 67 | ``` 68 | 69 | Some other handy functions are **ZEROP**, **MINUSP**, and **PLUSP**, which test 70 | whether a single real number is equal to, less than, or greater than 71 | zero. Two other predicates, **EVENP** and **ODDP**, test whether a single 72 | integer argument is even or odd. The `P` suffix on the names of these 73 | functions is a standard naming convention for predicate functions, 74 | functions that test some condition and return a boolean. 75 | 76 | 其他一些常用函数包括 **ZEROP**、**MINUSP** 和 77 | **PLUSP**,用来测试单一实数是否等于、小于或大于零。另外两个谓词 78 | **EVENP** 79 | 和 **ODDP**,测试单一整数参数是否是偶数或奇数。这些函数名称中的P后缀是一种谓词函数的标准命名约定,这些函数能够测试某些条件并返回一个布尔值。 80 | 81 | -------------------------------------------------------------------------------- /docsrc/chap10/strings.md: -------------------------------------------------------------------------------- 1 | # Strings(字符串) 2 | 3 | As mentioned earlier, strings in Common Lisp are really a composite 4 | data type, namely, a one-dimensional array of 5 | characters. Consequently, I'll cover many of the things you can do 6 | with strings in the next chapter when I discuss the many functions for 7 | manipulating sequences, of which strings are just one type. But 8 | strings also have their own literal syntax and a library of functions 9 | for performing string-specific operations. I'll discuss these aspects 10 | of strings in this chapter and leave the others for Chapter 11. 11 | 12 | 如前所述,Common Lisp 13 | 中的字符串其实是一种复合数据类型,也即一个一维字符数组。因此,我将在下一章讨论用来处理序列的许多函数时将谈及许多字符串应用,因为字符串只不过是一种序列。但字符串也有其自己的字面语法和一个用来进行字符串特定操作的函数数库。本章将讨论字符串的这些方面并将其余部分留到第 14 | 11 章再作介绍。 15 | 16 | As you've seen, literal strings are written enclosed in double 17 | quotes. You can include any character supported by the character set 18 | in a literal string except double quote (`"`) and backslash (`\`). And you 19 | can include these two as well if you escape them with a backslash. In 20 | fact, backslash always escapes the next character, whatever it is, 21 | though this isn't necessary for any character except for `"` and 22 | itself. Table 10-2 shows how various literal strings will be read by 23 | the Lisp reader. 24 | 25 | 正如你所看到的那样,字面字符串写在闭合的双引号里。你可以在一个字面字符串中包括任何字符集支持的字符,除了双引号(`"`)和反斜杠(`\`)。而如果你将它们用一个反斜杠转义的话也可以包括这两个字符。事实上,反斜杠总是转义其下一个字符,无论它是什么,尽管这对于除了 `"` 26 | 和它本身之外的其他字符并不必要。表 10-2 27 | 显示了不同的字面字符串是如何被 Lisp 读取器读取的。 28 | 29 | > Table 10-2. Literal Strings 30 | 31 | | Literal | Contents | Comment | 32 | | :------------- | :------- | :-------------------------------------------- | 33 | | `"foobar"` | foobar | Plain string. | 34 | | `"foo\"bar"` | foo"bar | The backslash escapes quote. | 35 | | `"foo\\bar"` | foo\bar | The first backslash escapes second backslash. | 36 | | `"\"foobar\""` | "foobar" | The backslashes escape quotes. | 37 | | `"foo\bar"` | foobar | The backslash "escapes" b | 38 | 39 | Note that the REPL will ordinarily print strings in readable form, 40 | adding the enclosing quotation marks and any necessary escaping 41 | backslashes, so if you want to see the actual contents of a string, 42 | you need to use function such as **FORMAT** designed to print 43 | human-readable output. For example, here's what you see if you type a 44 | string containing an embedded quotation mark at the REPL: 45 | 46 | 注意,REPL 47 | 将以可读的形式原样打印字符串,并带有外围的引号和任何必要的转义反斜杠。因此,如果想要看到一个字符串的实际内容,就要使用诸如FORMAT这种原本就是设计用于打印出可读性良好输出的函数。例如,下面是在 48 | REPL 中输入一个含有内嵌引号的字符串时所看到的输出: 49 | 50 | ```lisp 51 | CL-USER> "foo\"bar" 52 | "foo\"bar" 53 | ``` 54 | 55 | **FORMAT**, on the other hand, will show you the actual string contents: 56 | 57 | 另一方面,**FORMAT** 将显示出实际的字符串内容: 58 | 59 | ```lisp 60 | CL-USER> (format t "foo\"bar") 61 | foo"bar 62 | NIL 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /docsrc/chap11/chap11.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docsrc/chap11/collections.md: -------------------------------------------------------------------------------- 1 | # Collections(集合) 2 | 3 | Like most programming languages, Common Lisp provides standard data 4 | types that collect multiple values into a single object. Every 5 | language slices up the collection problem a little bit differently, 6 | but the basic collection types usually boil down to an integer-indexed 7 | array type and a table type that can be used to map more or less 8 | arbitrary keys to values. The former are variously called arrays, 9 | lists, or tuples; the latter go by the names hash tables, associative 10 | arrays, maps, and dictionaries. 11 | 12 | 和多数编程语言一样,Common Lisp 13 | 也提供了能将多个值收集到单一对象的标准数据类型。每一种语言在处理集合问题上都稍有不同,但基本的集合类型通常都可归结为一个整数索引的数组类型以及一个可将或多或少的任意关键字映射到值上的表类型。前者被分别称为数组(array)、列表(list)或元组(tuple),后者被命名为哈希表(hash table)、关联数组(associative array)、映射表(map)和字典(dictionary)。 14 | 15 | Lisp is, of course, famous for its list data structure, and most Lisp 16 | books, following the ontogeny-recapitulates-phylogeny principle of 17 | language instruction, start their discussion of Lisp's collections 18 | with lists. However, that approach often leads readers to the mistaken 19 | conclusion that lists are Lisp's only collection type. To make matters 20 | worse, because Lisp's lists are such a flexible data structure, it is 21 | possible to use them for many of the things arrays and hash tables are 22 | used for in other languages. But it's a mistake to focus too much on 23 | lists; while they're a crucial data structure for representing Lisp 24 | code as Lisp data, in many situations other data structures are more 25 | appropriate. 26 | 27 | 当然,Lisp 28 | 以其列表数据结构闻名于世,而多数遵循了语言用法的进化重演(ontogeny-recapitulates-phylogeny)原则的 29 | Lisp 教材也都从对基于列表的 Lisp 30 | 集合的讨论开始。尽管如此,这一观点通常导致读者错误地推论出列表是 31 | Lisp 的唯一集合类型。更糟糕的是,因为 Lisp 32 | 的列表是如此灵活的数据结构,它可被用于许多其他语言使用数组和哈希表的场合。但是将注意力过于集中在列表是上错误的,尽管它是一种将 33 | Lisp 代码作为 Lisp 34 | 数据来表达的关键数据结构,但在许多场合其他数据结构更合适。 35 | 36 | To keep lists from stealing the show, in this chapter I'll focus on 37 | Common Lisp's other collection types: vectors and hash tables. 38 | However, vectors and lists share enough characteristics that Common 39 | Lisp treats them both as subtypes of a more general abstraction, the 40 | sequence. Thus, you can use many of the functions I'll discuss in this 41 | chapter with both vectors and lists. 42 | 43 | 为了避免让列表过于出风头,在本章里我将集中在 Common Lisp 44 | 的其他集合类型上:向量和哈希表。 尽管如此,向量和列表共享了许多特征,因此 Common 45 | Lisp 将它们都视为一个更通用的抽象即序列的子类型。因此,你可以将本章我所讨论的许多函数同时用在向量和列表上。 46 | 47 | -------------------------------------------------------------------------------- /docsrc/chap11/hash-table-iteration.md: -------------------------------------------------------------------------------- 1 | # Hash Table Iteration(哈希表迭代) 2 | 3 | Common Lisp provides a couple ways to iterate over the entries in a 4 | hash table. The simplest of these is via the function 5 | **MAPHASH**. Analogous to the **MAP** function, **MAPHASH** takes a two-argument 6 | function and a hash table and invokes the function once for each 7 | key/value pair in the hash table. For instance, to print all the 8 | key/value pairs in a hash table, you could use **MAPHASH** like this: 9 | 10 | Common Lisp 11 | 提供了几种在哈希表项上迭代的方式。其中最简单的方式是通过函数 12 | **MAPHASH**。和 **MAP** 函数相似,**MAPHASH** 13 | 接受一个两参数函数和一个哈希表并在哈希表的每一个键值对上调用一次该函数。例如,为了打印哈希表中所有的键值对,你可以像这样来使用 **MAPHASH**: 14 | 15 | ```lisp 16 | (maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) *h*) 17 | ``` 18 | 19 | The consequences of adding or removing elements from a hash table 20 | while iterating over it aren't specified (and are likely to be bad) 21 | with two exceptions: you can use **SETF** with **GETHASH** to change the value 22 | of the current entry, and you can use **REMHASH** to remove the current 23 | entry. For instance, to remove all the entries whose value is less 24 | than ten, you could write this: 25 | 26 | 在迭代一个哈希表的过程中,向其中添加或移除元素的后果没有被指定(并且可能会很坏),但却有两个例外:你可以将 27 | **SETF** 与 **GETHASH** 28 | 一起使用来改变当前项的值,并且可以使用 **REMHASH** 29 | 来移除当前项。例如,为了移除所有其值小于 10 的项,你可以写成下面这样: 30 | 31 | ```lisp 32 | (maphash #'(lambda (k v) (when (< v 10) (remhash k *h*))) *h*) 33 | ``` 34 | 35 | The other way to iterate over a hash table is with the extended **LOOP** 36 | macro, which I'll discuss in Chapter 22. The **LOOP** equivalent of the 37 | first **MAPHASH** expression would look like this: 38 | 39 | 另一种在哈希表上迭代的方式是使用扩展的 **LOOP** 宏,我将在第 22 40 | 章里讨论它。第一个 **MAPHASH** 表达式的等价 **LOOP** 形式如下所示: 41 | 42 | ```lisp 43 | (loop for k being the hash-keys in *h* using (hash-value v) 44 | do (format t "~a => ~a~%" k v)) 45 | ``` 46 | 47 | I could say a lot more about the nonlist collections supported by 48 | Common Lisp. For instance, I haven't discussed multidimensional 49 | arrays at all or the library of functions for manipulating bit 50 | arrays. However, what I've covered in this chapter should suffice 51 | for most of your general-purpose programming needs. Now it's finally 52 | time to look at Lisp's eponymous data structure: lists. 53 | 54 | 关于 Common Lisp 55 | 所支持的非列表集合,我还可以讲更多的内容。例如,多维数组以及处理位数组的函数库。但本章中涉及到的内容将满足多数通用编程场合的需要。现在,终于可以介绍列表这个让 56 | Lisp 因此得名的数据结构了。 57 | -------------------------------------------------------------------------------- /docsrc/chap11/higher-order-function-variants.md: -------------------------------------------------------------------------------- 1 | # Higher-Order Function Variants(高阶函数变体) 2 | 3 | For each of the functions just discussed, Common Lisp provides two 4 | higher-order function variants that, in the place of the item 5 | argument, take a function to be called on each element of the 6 | sequence. One set of variants are named the same as the basic function 7 | with an **-IF** appended. These functions count, find, remove, and 8 | substitute elements of the sequence for which the function argument 9 | returns true. The other set of variants are named with an **-IF-NOT** 10 | suffix and count, find, remove, and substitute elements for which the 11 | function argument does not return true. 12 | 13 | 对于每个刚刚讨论过的函数,Common Lisp 14 | 都提供了两种高阶函数变体,它们接受一个将在每个序列元素上调用的函数,以此来代替项参数。一组变体被命名为与基本函数相同的名字并带有一个追加的 15 | **-IF**。这些函数用于计数、查找、移除以及替换序列中那些函数参数返回真的元素。另一类变体以 16 | **-IF-NOT** 后缀命名并计数、查找、移除以及替换函数参数不返回真的元素。 17 | 18 | ```lisp 19 | (count-if #'evenp #(1 2 3 4 5)) ==> 2 20 | 21 | (count-if-not #'evenp #(1 2 3 4 5)) ==> 3 22 | 23 | (position-if #'digit-char-p "abcd0001") ==> 4 24 | 25 | (remove-if-not #'(lambda (x) (char= (elt x 0) #\f)) 26 | #("foo" "bar" "baz" "foom")) ==> #("foo" "foom") 27 | ``` 28 | 29 | According to the language standard, the **-IF-NOT** variants are 30 | deprecated. However, that deprecation is generally considered to have 31 | itself been ill-advised. If the standard is ever revised, it's more 32 | likely the deprecation will be removed than the **-IF-NOT** 33 | functions. For one thing, the **REMOVE-IF-NOT** variant is probably used 34 | more often than **REMOVE-IF**. Despite its negative-sounding name, 35 | **REMOVE-IF-NOT** is actually the positive variant--it returns the 36 | elements that do satisfy the predicate. 37 | 38 | 根据语言标准,这些 **-IF-NOT** 39 | 变体已经过时了。但这种过时通常被认为是由于标准本身欠考虑。不过,如果标准被再次修订的话,更有可能被去掉的是 40 | **-IF** 而非 **-IF-NOT** 系列。比如说,有个叫 **REMOVE-IF-NOT** 41 | 变体就比 **REMOVE-IF** 更经常被使用。尽管它有一个听起来具有否定意义的名字,但 42 | **REMOVE-IF-NOT** 实际上是一个具有肯定意义的变体——它返回满足谓词的那些元素。 43 | 44 | The **-IF** and **-IF-NOT** variants accept all the same keyword arguments as 45 | their vanilla counterparts except for `:test`, which isn't needed since 46 | the main argument is already a function. With a `:key` argument, the 47 | value extracted by the `:key` function is passed to the function instead 48 | of the actual element. 49 | 50 | 除了 `:test`,这些 **-IF** 和 **-IF-NOT** 51 | 变体都接受和它们的原始版本相同的关键字参数,`:test` 52 | 不再被需要是因为主参数已经是一个函数了。 通过使用 `:key` 53 | 参数,由 `:key` 函数所抽取出的值将代替实际元素传递给该函数。 54 | 55 | ```lisp 56 | (count-if #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) ==> 2 57 | 58 | (count-if-not #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) ==> 3 59 | 60 | (remove-if-not #'alpha-char-p 61 | #("foo" "bar" "1baz") :key #'(lambda (x) (elt x 0))) ==> #("foo" "bar") 62 | ``` 63 | 64 | The **REMOVE** family of functions also support a fourth variant, 65 | **REMOVE-DUPLICATES**, that has only one required argument, a sequence, 66 | from which it removes all but one instance of each duplicated 67 | element. It takes the same keyword arguments as **REMOVE**, except for 68 | `:count`, since it always removes all duplicates. 69 | 70 | **REMOVE** 函数家族还支持第四个变体 71 | **REMOVE-DUPLICATES**,其接受一个序列作为唯一的必要参数,并将其中每个重复的元素移除到只剩下一个实例。它和 72 | **REMOVE** 接受相同的关键字参数,除了 73 | `:count`,因为它总是删除所有的重复。 74 | 75 | ```lisp 76 | (remove-duplicates #(1 2 1 2 3 1 2 3 4)) ==> #(1 2 3 4) 77 | ``` 78 | -------------------------------------------------------------------------------- /docsrc/chap11/sequence-mapping-functions.md: -------------------------------------------------------------------------------- 1 | # Sequence Mapping Functions(序列映射函数) 2 | 3 | Finally, the last of the sequence functions are the generic mapping 4 | functions. **MAP**, like the sequence predicate functions, takes a 5 | n-argument function and n sequences. But instead of a boolean value, 6 | **MAP** returns a new sequence containing the result of applying the 7 | function to subsequent elements of the sequences. Like **CONCATENATE** and 8 | **MERGE**, **MAP** needs to be told what kind of sequence to create. 9 | 10 | 最终,序列函数的最后是通用映射函数。**MAP** 11 | 和序列谓词函数一样,接受一个带有 n 12 | 个参数函数和 n 个序列。但并非返回布尔值,**MAP** 13 | 返回一个新序列,它由那些将函数应用在序列的相继元素上所得到的结果组成。与 14 | **CONCATENATE** 和 **MERGE** 相似,**MAP** 需要被告知其所创建序列的类型。 15 | 16 | ```lisp 17 | (map 'vector #'* #(1 2 3 4 5) #(10 9 8 7 6)) ==> #(10 18 24 28 30) 18 | ``` 19 | 20 | **MAP-INTO** is like **MAP** except instead of producing a new 21 | sequence of a given type, it places the results into a sequence 22 | passed as the first argument. This sequence can be the same as one 23 | of the sequences providing values for the function. For instance, to 24 | sum several vectors--`a`, `b`, and `c`--into one, you could write this: 25 | 26 | **MAP-INTO** 和 **MAP** 27 | 相似,但它并不产生给定类型的新序列,而是将结果放置在一个作为第一个参数传递的序列中。这个序列可以和为函数提供值的序列中的一个相同。例如,为了将几个向量 28 | `a`、`b` 和 `c` 相加到其中一个向量里,你可以写成这样: 29 | 30 | ```lisp 31 | (map-into a #'+ a b c) 32 | ``` 33 | 34 | If the sequences are different lengths, **MAP-INTO** affects only as many 35 | elements as are present in the shortest sequence, including the 36 | sequence being mapped into. However, if the sequence being mapped into 37 | is a vector with a fill pointer, the number of elements affected isn't 38 | limited by the fill pointer but rather by the actual size of the 39 | vector. After a call to **MAP-INTO**, the fill pointer will be set to the 40 | number of elements mapped. **MAP-INTO** won't, however, extend an 41 | adjustable vector. 42 | 43 | 如果这些序列的长度不同,那么 **MAP-INTO** 44 | 将只影响那些与最短序列元素数量相当元素,其中也包括那个将被映射到的序列。不过,如果序列被映射到一个带有填充指针的向量里,受影响元素的数量将不限于填充指针而是该向量的实际大小。在一个对 45 | **MAP-INTO** 46 | 的调用之后,填充指针将被设置成被映射元素的数量。尽管如此,**MAP-INTO** 47 | 将不会扩展一个可调整大小的向量。 48 | 49 | The last sequence function is **REDUCE**, which does another kind of 50 | mapping: it maps over a single sequence, applying a two-argument 51 | function first to the first two elements of the sequence and then to 52 | the value returned by the function and subsequent elements of the 53 | sequence. Thus, the following expression sums the numbers from one to 54 | ten: 55 | 56 | 最后一个序列函数是 57 | **REDUCE**,它可以做另一种类型的映射:映射在单个序列上,先将一个两参数函数应用到序列的最初两个元素上,再将函数返回的值和序列的后续元素继续用于该函数。这样,下面的表达式将对从 58 | 1 到 10 的整数求和: 59 | 60 | ```lisp 61 | (reduce #'+ #(1 2 3 4 5 6 7 8 9 10)) ==> 55 62 | ``` 63 | 64 | **REDUCE** is a surprisingly useful function--whenever you need to 65 | distill a sequence down to a single value, chances are you can write 66 | it with **REDUCE**, and it will often be quite a concise way to express 67 | what you want. For instance, to find the maximum value in a sequence 68 | of numbers, you can write `(reduce #'max numbers)`. **REDUCE** also takes 69 | a full complement of keyword arguments (`:key`, `:from-end`, `:start`, and 70 | `:end`) and one unique to **REDUCE** (`:initial-value`). The latter 71 | specifies a value that's logically placed before the first element 72 | of the sequence (or after the last if you also specify a true 73 | `:from-end` argument). 74 | 75 | **REDUCE** 76 | 函数非常有用,无论何时,当需要将一个序列提炼成一个单独的值时,你都有机会用 77 | **REDUCE** 78 | 来写它,而这通常都是一种相当简洁的表达意图的方法。例如,为了找出一个数字序列中的最大值,你可以写成 79 | `(reduce #'max numbers)`。**REDUCE** 80 | 也接受完整的关键字参数(`:key`、`:from-end`、`:start` 和 `:end`)以及一个 81 | **REDUCE** 专用的 82 | `:intial-value`。后者可以指定一个值,在逻辑上被放置在序列的第一个元素之前(或是如果你同时指定了一个为真的 83 | `:from-end` 参数,那么该值被放置在序列的最后一个元素之后)。 84 | -------------------------------------------------------------------------------- /docsrc/chap11/sequence-predicates.md: -------------------------------------------------------------------------------- 1 | # Sequence Predicates(序列谓词) 2 | 3 | Four other handy functions are **EVERY**, **SOME**, **NOTANY**, and **NOTEVERY**, 4 | which iterate over sequences testing a boolean predicate. The first 5 | argument to all these functions is the predicate, and the remaining 6 | arguments are sequences. The predicate should take as many arguments 7 | as the number of sequences passed. The elements of the sequences are 8 | passed to the predicate--one element from each sequence--until one of 9 | the sequences runs out of elements or the overall termination test is 10 | met: **EVERY** terminates, returning false, as soon as the predicate 11 | fails. If the predicate is always satisfied, it returns true. **SOME** 12 | returns the first non-**NIL** value returned by the predicate or returns 13 | false if the predicate is never satisfied. **NOTANY** returns false as 14 | soon as the predicate is satisfied or true if it never is. And 15 | **NOTEVERY** returns true as soon as the predicate fails or false if the 16 | predicate is always satisfied. Here are some examples of testing just 17 | one sequence: 18 | 19 | 另外四个常见的函数是 **EVERY**、**SOME**、**NOTANY** 和 20 | **NOTEVERY**,它们在序列上迭代并测试一个布尔谓词。所有这些函数的第一参数是一个谓词,其余的参数都是序列。这个谓词应当接受与所传递序列相同数量的参数。序列的元素被传递给该谓词,每个序列中各取出一个元素,直到某个序列用完所有的元素或是整体终止测试条件被满足:**EVERY** 21 | 在谓词失败时返回假。如果谓词总被满足,它返回真。**SOME** 22 | 返回由谓词所返回的第一个非NIL值,或者在谓词永远得不到满足时返回假。**NOTANY** 23 | 将在谓词满足时返回假,或者在从未满足时返回真。而 24 | **NOTEVERY** 25 | 在谓词失败时返回真,或是在谓词总是满足时返回假。下面是一些仅测试在一个序列上的例子: 26 | 27 | ```lisp 28 | (every #'evenp #(1 2 3 4 5)) ==> NIL 29 | (some #'evenp #(1 2 3 4 5)) ==> T 30 | (notany #'evenp #(1 2 3 4 5)) ==> NIL 31 | (notevery #'evenp #(1 2 3 4 5)) ==> T 32 | ``` 33 | 34 | These calls compare elements of two sequences pairwise: 35 | 36 | 下面的调用比较成对的两个序列中的元素: 37 | 38 | ```lisp 39 | (every #'> #(1 2 3 4) #(5 4 3 2)) ==> NIL 40 | (some #'> #(1 2 3 4) #(5 4 3 2)) ==> T 41 | (notany #'> #(1 2 3 4) #(5 4 3 2)) ==> NIL 42 | (notevery #'> #(1 2 3 4) #(5 4 3 2)) ==> T 43 | ``` 44 | -------------------------------------------------------------------------------- /docsrc/chap11/sorting-and-merging.md: -------------------------------------------------------------------------------- 1 | # Sorting and Merging(排序与合并) 2 | 3 | The functions **SORT** and **STABLE-SORT** provide two ways of sorting 4 | a sequence. They both take a sequence and a two-argument predicate and 5 | return a sorted version of the sequence. 6 | 7 | 函数 **SORT** 和 **STABLE-SORT** 8 | 提供了两种序列排序方式。它们都接受一个序列和一个由两个实参组成的谓词并返回该序列排序后的版本。 9 | 10 | ```lisp 11 | (sort (vector "foo" "bar" "baz") #'string<) ==> #("bar" "baz" "foo") 12 | ``` 13 | 14 | The difference is that **STABLE-SORT** is guaranteed to not reorder any 15 | elements considered equivalent by the predicate while **SORT** guarantees 16 | only that the result is sorted and may reorder equivalent elements. 17 | 18 | 它们的区别在于,**STABLE-SORT** 19 | 可以保证不会重排任何被该谓词视为等价的元素,而 **SORT** 20 | 只保证结果是排序了的并可能重排等价元素。 21 | 22 | Both these functions are examples of what are called destructive 23 | functions. Destructive functions are allowed--typically for reasons of 24 | efficiency--to modify their arguments in more or less arbitrary 25 | ways. This has two implications: one, you should always do something 26 | with the return value of these functions (such as assign it to a 27 | variable or pass it to another function), and, two, unless you're done 28 | with the object you're passing to the destructive function, you should 29 | pass a copy instead. I'll say more about destructive functions in the 30 | next chapter. 31 | 32 | 这两个函数都是所谓的破坏性(destructive)函数。通常出于效率的原因,破坏性函数都会或多或少地修改它们的参数。这有两层含义:第一,你应该总是对这些函数的返回值做一些事情(比如给它赋值一个变量或将它传递给另一个函数);第二,除非你不再需要你传给破坏性函数的那个对象,否则你应该代替地传递一个副本。下一章里将会讨论更多有关破坏性函数的内容。 33 | 34 | Typically you won't care about the unsorted version of a sequence 35 | after you've sorted it, so it makes sense to allow **SORT** and 36 | **STABLE-SORT** to destroy the sequence in the course of sorting it. But 37 | it does mean you need to remember to write the following: 38 | 39 | 通常在排序以后你不会再关心那个序列的未排序版本,因此在排序的过程中,允许 40 | **SORT** 和 **STABLE-SORT** 破坏序列是合理的。但这意味着需要记得像这样来写: 41 | 42 | ```lisp 43 | (setf my-sequence (sort my-sequence #'string<)) 44 | ``` 45 | 46 | rather than just this: 47 | 48 | 而不是这样: 49 | 50 | ```lisp 51 | (sort my-sequence #'string<) 52 | ``` 53 | 54 | Both these functions also take a keyword argument, `:key`, which, like 55 | the `:key` argument in other sequence functions, should be a function 56 | and will be used to extract the values to be passed to the sorting 57 | predicate in the place of the actual elements. The extracted keys are 58 | used only to determine the ordering of elements; the sequence returned 59 | will contain the actual elements of the argument sequence. 60 | 61 | 这两个函数也接受关键字参数 `:key`,它和其他序列函数的 `:key` 62 | 参数一样,应当是一个将被用来从序列元素中抽取出传给排序谓词的值的函数。被抽取出的关键字仅用于确定元素顺序,返回的序列将含有参数序列的实际元素。 63 | 64 | The **MERGE** function takes two sequences and a predicate and returns a 65 | sequence produced by merging the two sequences, according to the 66 | predicate. It's related to the two sorting functions in that if each 67 | sequence is already sorted by the same predicate, then the sequence 68 | returned by **MERGE** will also be sorted. Like the sorting functions, 69 | **MERGE** takes a `:key` argument. Like **CONCATENATE**, and for the same 70 | reason, the first argument to **MERGE** must be a type descriptor 71 | specifying the type of sequence to produce. 72 | 73 | 函数 **MERGE** 74 | 接受两个序列和一个谓词并返回按照该谓词合并两个序列所产生的序列。它和两个排序函数之间的关系在于,如果每个序列已经被同样的谓词排序过了,那么由 75 | **MERGE** 返回的序列也将是有序的。和排序函数一样,**MERGE** 也接受一个 76 | `:key` 参数。并且出于同样原因,和 **CONCATENATE** 77 | 一样,**MERGE** 的第一个参数必须是用来指定所生成序列类型的类型描述符。 78 | 79 | ```lisp 80 | (merge 'vector #(1 3 5) #(2 4 6) #'<) ==> #(1 2 3 4 5 6) 81 | (merge 'list #(1 3 5) #(2 4 6) #'<) ==> (1 2 3 4 5 6) 82 | ``` 83 | -------------------------------------------------------------------------------- /docsrc/chap11/subsequence-manipulations.md: -------------------------------------------------------------------------------- 1 | # Subsequence Manipulations(操纵子序列) 2 | 3 | Another set of functions allows you to manipulate subsequences of 4 | existing sequences. The most basic of these is **SUBSEQ**, which extracts 5 | a subsequence starting at a particular index and continuing to a 6 | particular ending index or the end of the sequence. For instance: 7 | 8 | 另一类函数允许你对已有序列的子序列进行操作。其中最基本的是 9 | **SUBSEQ**,其解出序列中从一个特定索引开始并延续到一个特定终止索引或结尾处的子序列。例如: 10 | 11 | ```lisp 12 | (subseq "foobarbaz" 3) ==> "barbaz" 13 | (subseq "foobarbaz" 3 6) ==> "bar" 14 | ``` 15 | 16 | **SUBSEQ** is also **SETF**able, but it won't extend or shrink a 17 | sequence; if the new value and the subsequence to be replaced are 18 | different lengths, the shorter of the two determines how many 19 | characters are actually changed. 20 | 21 | **SUBSEQ** 也支持 22 | **SETF**,但不会扩大或缩小一个序列。如果新的值和将被替换的子序列具有不同的长度,那么两者中较短的那一个将决定有多少个字符被实际改变。 23 | 24 | ```lisp 25 | (defparameter *x* (copy-seq "foobarbaz")) 26 | 27 | (setf (subseq *x* 3 6) "xxx") ; subsequence and new value are same length 28 | *x* ==> "fooxxxbaz" 29 | 30 | (setf (subseq *x* 3 6) "abcd") ; new value too long, extra character ignored. 31 | *x* ==> "fooabcbaz" 32 | 33 | (setf (subseq *x* 3 6) "xx") ; new value too short, only two characters changed 34 | *x* ==> "fooxxcbaz" 35 | ``` 36 | 37 | You can use the **FILL** function to set multiple elements of a 38 | sequence to a single value. The required arguments are a sequence and 39 | the value with which to fill it. By default every element of the 40 | sequence is set to the value; `:start` and `:end` keyword arguments can 41 | limit the effects to a given subsequence. 42 | 43 | 你可以使用 **FILL** 44 | 函数来将一个序列的多个元素设置到单个值上。所需的参数是一个序列以及所填充的值。在默认情况下,该序列的每个元素都被设置到该值上。`:start` 45 | 和 `:end` 关键字参数可以将效果限制在一个给定的子序列上。 46 | 47 | If you need to find a subsequence within a sequence, the **SEARCH** 48 | function works like **POSITION** except the first argument is a sequence 49 | rather than a single item. 50 | 51 | 如果你需要在一个序列中查找一个子序列,**SEARCH** 52 | 函数可以像 **POSITION** 那样工作,不过第一个参数是一个序列而不是一个单独的项。 53 | 54 | ```lisp 55 | (position #\b "foobarbaz") ==> 3 56 | (search "bar" "foobarbaz") ==> 3 57 | ``` 58 | 59 | On the other hand, to find where two sequences with a common prefix 60 | first diverge, you can use the **MISMATCH** function. It takes two 61 | sequences and returns the index of the first pair of mismatched 62 | elements. 63 | 64 | 另一方面,为了找出两个带有相同前缀的序列首次分岔的位置,你可以使用 65 | **MISMATCH** 函数。它接受两个序列并返回第一对不相匹配的元素的索引。 66 | 67 | ```lisp 68 | (mismatch "foobarbaz" "foom") ==> 3 69 | ``` 70 | 71 | It returns **NIL** if the strings match. **MISMATCH** also takes many of the 72 | standard keyword arguments: a `:key` argument for specifying a function 73 | to use to extract the values to be compared; a `:test` argument to 74 | specify the comparison function; and `:start1`, `:end1`, `:start2`, and 75 | `:end2` arguments to specify subsequences within the two sequences. And 76 | a `:from-end` argument of **T** specifies the sequences should be searched 77 | in reverse order, causing **MISMATCH** to return the index, in the first 78 | sequence, where whatever common suffix the two sequences share begins. 79 | 80 | 如果字符串匹配,它将返回 **NIL**。**MISMATCH** 81 | 也接受许多标准关键字参数:`:key` 82 | 参数可以指定一个函数用来抽取出被比较的值;`:test` 83 | 参数用于指定比较函数;而 `:start1`、`:end1`、`:start2` 和 `:end2` 84 | 参数可以指定两个序列中的子序列。另外,一个设置为 **T** 85 | 的 `:from-end` 86 | 参数可以指定以相反的顺序搜索序列,从而导致 **MISMATCH** 87 | 返回两个序列的相同后缀在第一个序列中开始位置的索引。 88 | 89 | ```lisp 90 | (mismatch "foobar" "bar" :from-end t) ==> 3 91 | ``` 92 | -------------------------------------------------------------------------------- /docsrc/chap11/subtypes-of-vector.md: -------------------------------------------------------------------------------- 1 | # Subtypes of Vector(向量的子类型) 2 | 3 | All the vectors you've dealt with so far have been general vectors 4 | that can hold any type of object. It's also possible to create 5 | specialized vectors that are restricted to holding certain types of 6 | elements. One reason to use specialized vectors is they may be stored 7 | more compactly and can provide slightly faster access to their 8 | elements than general vectors. However, for the moment let's focus on 9 | a couple kinds of specialized vectors that are important data types in 10 | their own right. 11 | 12 | 目前为止,你处理的所有向量都是可以保存任意类型对象的通用向量。也可以创建特化的向量使其仅限于保存特定类型的元素。使用特化向量的理由之一是,它们可以更加紧凑地存储,并且可以比通用向量提供对其元素更快速的访问。不过目前我们将集中介绍几类特化向量,它们本身就是重要的数据类型。 13 | 14 | One of these you've seen already--strings are vectors specialized to 15 | hold characters. Strings are important enough to get their own 16 | read/print syntax (double quotes) and the set of string-specific 17 | functions I discussed in the previous chapter. But because they're 18 | also vectors, all the functions I'll discuss in the next few sections 19 | that take vector arguments can also be used with strings. These 20 | functions will fill out the string library with functions for things 21 | such as searching a string for a substring, finding occurrences of a 22 | character within a string, and more. 23 | 24 | 其中一类你已经见过了,这就是字符串,它是特定用来保存字符的向量。字符串特别重要,以至于它们到有自己的读写语法(双引号)和一组特定于字符串的函数前一章已讨论过。但因为它们也是向量,所有接下来几节里所讨论的接受向量实参的函数也可以用在字符串上。这些函数将为字符串函数库带来新的功能,例如用一个子串来搜索字符串,查找一个字符在字符串中出现的次数,等等。 25 | 26 | Literal strings, such as "foo", are like literal vectors written with 27 | the `#()` syntax--their size is fixed, and they must not be 28 | modified. However, you can use **MAKE-ARRAY** to make resizable strings by 29 | adding another keyword argument, `:element-type`. This argument takes a 30 | type descriptor. I won't discuss all the possible type descriptors you 31 | can use here; for now it's enough to know you can create a string by 32 | passing the symbol **CHARACTER** as the `:element-type` argument. Note that 33 | you need to quote the symbol to prevent it from being treated as a 34 | variable name. For example, to make an initially empty but resizable 35 | string, you can write this: 36 | 37 | 诸如 “foo” 38 | 这样的字面字符串,和那些用 `#()` 39 | 语法写成的字面向量一样,其大小都是固定的,并且它们根本不能被修改。但你可以用 **MAKE-ARRAY** 40 | 通过添加另一个关键字参数 `:element-type` 41 | 来创建变长字符串。该参数接受一个类型描述符。我将不会介绍你可以在这里使用的所有可能的类型描述符,目前只需知道你可以通过传递符号 42 | **CHARACTER** 作为 `:element-type` 43 | 来创建字符串。注意,你需要引用该符号以避免它被视为一个变量名。例如,创建一个初始为空但却变长的字符串如下所示: 44 | 45 | ```lisp 46 | (make-array 5 :fill-pointer 0 :adjustable t :element-type 'character) ==> "" 47 | ``` 48 | 49 | Bit vectors--vectors whose elements are all zeros or ones--also get 50 | some special treatment. They have a special read/print syntax that 51 | looks like `#*00001111` and a fairly large library of functions, which I 52 | won't discuss, for performing bit-twiddling operations such as 53 | "anding" together two bit arrays. The type descriptor to pass as the 54 | :element-type to create a bit vector is the symbol **BIT**. 55 | 56 | 位向量是元素全部由 0 或 1 57 | 所组成的向量,它也得到一些特殊对待。它们有一个特别的读/写语法,看起来像 58 | `#*00001111`,另外还有一个相对巨大的函数库。这些函数可用于按位操作,例如将两个位数组 59 | “与” 在一起(这里不会介绍)。用来创建一个位向量传递给 `:element-type` 60 | 的类型描述符是符号 **BIT**。 61 | 62 | -------------------------------------------------------------------------------- /docsrc/chap11/vector-as-sequences.md: -------------------------------------------------------------------------------- 1 | # Vectors As Sequences(作为序列的向量) 2 | 3 | As mentioned earlier, vectors and lists are the two concrete subtypes 4 | of the abstract type sequence. All the functions I'll discuss in the 5 | next few sections are sequence functions; in addition to being 6 | applicable to vectors--both general and specialized--they can also be 7 | used with lists. 8 | 9 | 正如早先所提到的,向量和列表是抽象类型序列的两种具体子类型。接下来几节里讨论的所有函数都是序列函数:除了可以应用于向量(无论是通用还是特化的)之外,它们还可应用于列表。 10 | 11 | The two most basic sequence functions are **LENGTH**, which returns the 12 | length of a sequence, and **ELT**, which allows you to access individual 13 | elements via an integer index. **LENGTH** takes a sequence as its only 14 | argument and returns the number of elements it contains. For vectors 15 | with a fill pointer, this will be the value of the fill pointer. **ELT**, 16 | short for element, takes a sequence and an integer index between zero 17 | (inclusive) and the length of the sequence (exclusive) and returns the 18 | corresponding element. **ELT** will signal an error if the index is out of 19 | bounds. Like **LENGTH**, **ELT** treats a vector with a fill pointer as having 20 | the length specified by the fill pointer. 21 | 22 | 两个最基本的序列函数是 23 | **LENGTH**,其返回一个序列的长度;**ELT**,其允许通过一个整数索引来访问个别元素。**LENGTH** 24 | 接受序列作为其唯一的参数并返回它含有的元素数量。对于带有填充指针的向量,这些是填充指针的值。**ELT** 25 | 是元素(element)的简称,它接受序列和从 0 26 | 到序列长度(左闭右开区间)的整数索引并返回对应的元素。**ELT** 27 | 将在索引超出边界时报错。和 **LENGTH** 一样,**ELT** 28 | 也将把一个带有填充指针的向量视为其具有该填充指针所指定的长度。 29 | 30 | ```lisp 31 | (defparameter *x* (vector 1 2 3)) 32 | 33 | (length *x*) ==> 3 34 | (elt *x* 0) ==> 1 35 | (elt *x* 1) ==> 2 36 | (elt *x* 2) ==> 3 37 | (elt *x* 3) ==> error 38 | ``` 39 | 40 | **ELT** is also a **SETF**able place, so you can set the value of a 41 | particular element like this: 42 | 43 | **ELT** 也是一个支持 **SETF** 的位置,因此可以像这样来设置一个特定元素的值: 44 | 45 | ```lisp 46 | (setf (elt *x* 0) 10) 47 | 48 | *x* ==> #(10 2 3) 49 | ``` 50 | -------------------------------------------------------------------------------- /docsrc/chap11/whole-sequence-manipulations.md: -------------------------------------------------------------------------------- 1 | # Whole Sequence Manipulations(针对整个序列的操作) 2 | 3 | A handful of functions perform operations on a whole sequence (or 4 | sequences) at a time. These tend to be simpler than the other 5 | functions I've described so far. For instance, **COPY-SEQ** and **REVERSE** 6 | each take a single argument, a sequence, and each returns a new 7 | sequence of the same type. The sequence returned by **COPY-SEQ** contains 8 | the same elements as its argument while the sequence returned by 9 | **REVERSE** contains the same elements but in reverse order. Note that 10 | neither function copies the elements themselves--only the returned 11 | sequence is a new object. 12 | 13 | 有一些函数可以一次在整个序列(或多个序列)上进行操作。这些函数比目前为止我已描述的其他函数简单一些。例如,**COPY-SEQ** 14 | 和 **REVERSE** 都接受单一的序列参数并返回一个相同类型的新序列。**COPY-SEQ** 15 | 返回的序列包含与其参数相同的元素,而 **REVERSE** 16 | 返回的序列则含有顺序相反的相同元素。注意,这两个函数都不会复制元素本身,只有返回的序列是一个新对象。 17 | 18 | The **CONCATENATE** function creates a new sequence containing the 19 | concatenation of any number of sequences. However, unlike **REVERSE** and 20 | **COPY-SEQ**, which simply return a sequence of the same type as their 21 | single argument, **CONCATENATE** must be told explicitly what kind of 22 | sequence to produce in case the arguments are of different types. Its 23 | first argument is a type descriptor, like the `:element-type` argument 24 | to **MAKE-ARRAY**. In this case, the type descriptors you'll most likely 25 | use are the symbols **VECTOR**, **LIST**, or **STRING**. For example: 26 | 27 | 函数 **CONCATENATE** 28 | 创建一个将任意数量序列连接在一起的新序列。不过,跟 **REVERSE** 和 **COPY-SEQ** 29 | 简单地返回与其单一参数相同类型序列有所不同的是,**CONCATENATE** 30 | 必须被显式指定产生何种类型的序列,因为其参数可能是不同类型的。它的第一个参数是类型描述符,就像是 31 | **MAKE-ARRAY** 的 `:element-type` 32 | 参数那样。这里最常用到的类型描述符是符号 **VECTOR**、**LIST** 33 | 和 **STRING**。 例如: 34 | 35 | ```lisp 36 | (concatenate 'vector #(1 2 3) '(4 5 6)) ==> #(1 2 3 4 5 6) 37 | (concatenate 'list #(1 2 3) '(4 5 6)) ==> (1 2 3 4 5 6) 38 | (concatenate 'string "abc" '(#\d #\e #\f)) ==> "abcdef" 39 | ``` 40 | -------------------------------------------------------------------------------- /docsrc/chap12/after-append.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap12/after-append.png -------------------------------------------------------------------------------- /docsrc/chap12/chap12.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docsrc/chap12/list-1-2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap12/list-1-2-3.png -------------------------------------------------------------------------------- /docsrc/chap12/list-processing.md: -------------------------------------------------------------------------------- 1 | # They Called It LISP for a Reason: List Processing(LISP 名字的由来:列表处理) 2 | 3 | Lists play an important role in Lisp--for reasons both historical and 4 | practical. Historically, lists were Lisp's original composite data 5 | type, though it has been decades since they were its only such data 6 | type. These days, a Common Lisp programmer is as likely to use a 7 | vector, a hash table, or a user-defined class or structure as to use a 8 | list. 9 | 10 | 无论是出于历史还是实践的的原因,列表在 Lisp 11 | 中都扮演着重要的角色。在历史上,列表曾经是 Lisp 12 | 最初的复合数据类型,尽管很多年从实践上来它是这方面唯一的数据类型。现在,Lisp 13 | 程序员可能会使用向量、哈希表、用户自定义的类或者结构体来代替列表。 14 | 15 | Practically speaking, lists remain in the language because they're an 16 | excellent solution to certain problems. One such problem--how to 17 | represent code as data in order to support code-transforming and 18 | code-generating macros--is particular to Lisp, which may explain why 19 | other languages don't feel the lack of Lisp-style lists. More 20 | generally, lists are an excellent data structure for representing any 21 | kind of heterogeneous and/or hierarchical data. They're also quite 22 | lightweight and support a functional style of programming that's 23 | another important part of Lisp's heritage. 24 | 25 | 从实践上来讲,由于列表对特定问题提供了极佳的解决方案,因此它们仍然能留在语言之中。其中有这样一个问题——如何将代码表示成数据从而支持代码转换和生成代码的宏,它就是特定于 26 | Lisp 的,这就可以解释为什么其他语言没有感觉到因缺少 Lisp 27 | 式列表所带来的不便。更一般地讲,列表是用于表达任何异构和(或)层次数据的极佳数据结构。它们相当轻量并且支持函数式的编程风格,而这种编程风格也是 28 | Lisp 传统的另一个重要方面。 29 | 30 | Thus, you need to understand lists on their own terms; as you gain a 31 | better understanding of how lists work, you'll be in a better position 32 | to appreciate when you should and shouldn't use them. 33 | 34 | 因此,你需要更加深入地理解列表。一旦对列表的工作方式有了更加深刻的理解,你将会对如何适时地使用它们有更好的认识。 35 | 36 | -------------------------------------------------------------------------------- /docsrc/chap12/mixed-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap12/mixed-list.png -------------------------------------------------------------------------------- /docsrc/chap12/one-cons-cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap12/one-cons-cell.png -------------------------------------------------------------------------------- /docsrc/chap12/other-structures.md: -------------------------------------------------------------------------------- 1 | # Other Structures(其他结构) 2 | 3 | While cons cells and lists are typically considered to be synonymous, 4 | that's not quite right--as I mentioned earlier, you can use lists of 5 | lists to represent trees. Just as the functions discussed in this 6 | chapter allow you to treat structures built out of cons cells as 7 | lists, other functions allow you to use cons cells to represent trees, 8 | sets, and two kinds of key/value maps. I'll discuss some of those 9 | functions in the next chapter. 10 | 11 | 尽管点对单元和列表通常被视作同义词,但这并不很准确。正如我早先提到的,你可以使用列表的列表来表示树。正如本章所讨论的函数允许你将构建在点对单元上的结构视为列表那样,其他函数也允许你使用点对单元来表示树、集合以及两类键/值映射表。我将在第 12 | 13 章讨论这类函数中的一些。 13 | -------------------------------------------------------------------------------- /docsrc/chap13/alist-abc-123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap13/alist-abc-123.png -------------------------------------------------------------------------------- /docsrc/chap13/beyond-lists.md: -------------------------------------------------------------------------------- 1 | # Beyond Lists: Other Uses for Cons Cells(超越列表:点对单元的其他用法) 2 | 3 | As you saw in the previous chapter, the list data type is an illusion 4 | created by a set of functions that manipulate cons cells. Common Lisp 5 | also provides functions that let you treat data structures built out 6 | of cons cells as trees, sets, and lookup tables. In this chapter I'll 7 | give you a quick tour of some of these other data structures and the 8 | functions for manipulating them. As with the list-manipulation 9 | functions, many of these functions will be useful when you start 10 | writing more complicated macros and need to manipulate Lisp code as 11 | data. 12 | 13 | 如同你在前面章节里看到的,列表数据类型是由一组操作点对单元的函数描述的。另外,Common 14 | Lisp 15 | 还提供了一些函数;它们可使你把点对单元构建出的数据结构看作树、集合及查询表。本章将简要介绍这其中的一些数据结构及其处理函数。和列表处理函数一样,在开始编写更复杂的宏以及需要将 16 | Lisp 代码作为数据处理时,这其中有很多函数会很有用。 17 | -------------------------------------------------------------------------------- /docsrc/chap13/chap13.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docsrc/chap13/copy-list-list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap13/copy-list-list-or-tree.png -------------------------------------------------------------------------------- /docsrc/chap13/copy-tree-list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap13/copy-tree-list-or-tree.png -------------------------------------------------------------------------------- /docsrc/chap13/list-or-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap13/list-or-tree.png -------------------------------------------------------------------------------- /docsrc/chap13/plist-abc-123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap13/plist-abc-123.png -------------------------------------------------------------------------------- /docsrc/chap14/bulk-reads.md: -------------------------------------------------------------------------------- 1 | # Bulk Reads(批量读取) 2 | 3 | One last reading function, **READ-SEQUENCE**, works with both character 4 | and binary streams. You pass it a sequence (typically a vector) and a 5 | stream, and it attempts to fill the sequence with data from the 6 | stream. It returns the index of the first element of the sequence that 7 | wasn't filled or the length of the sequence if it was able to 8 | completely fill it. You can also pass `:start` and `:end` keyword 9 | arguments to specify a subsequence that should be filled instead. The 10 | sequence argument must be a type that can hold elements of the 11 | stream's element type. Since most operating systems support some form 12 | of block I/O, **READ-SEQUENCE** is likely to be quite a bit more efficient 13 | than filling a sequence by repeatedly calling **READ-BYTE** or **READ-CHAR**. 14 | 15 | 最后一个读取函数 **READ-SEQUENCE** 16 | 可同时工作在字符和二进制流上。你传递给它一个序列(通常是一个向量)和一个流,然后它会尝试用来自流的数据填充该序列。它返回序列中第一个没有被填充的元素的索引,或是在完全填充的情况下返回该序列的长度。你也可以传递 17 | `:start` 和 `:end` 18 | 关键字参数来指定一个应当被代替填充的子序列。该序列参数的元素类型必须足 19 | 以保存带有该流元素类型的元素。由于多数操作系统支持某种形式的块 20 | I/O,**READ-SEQUENCE** 通常比重复调用 21 | **READ-BYTE** 或 **READ-CHAR** 来填充一个序列更加高效。 22 | -------------------------------------------------------------------------------- /docsrc/chap14/chap14.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docsrc/chap14/filenames.md: -------------------------------------------------------------------------------- 1 | # Filenames(文件名) 2 | 3 | So far you've used strings to represent filenames. However, using 4 | strings as filenames ties your code to a particular operating system 5 | and file system. Likewise, if you programmatically construct names 6 | according to the rules of a particular naming scheme (separating 7 | directories with `/`, say), you also tie your code to a particular file 8 | system. 9 | 10 | 到目前为止,文件名都是用字符串表示的。但使用字符串作为文件名会将代码捆绑在特定操作系统和文件系统上。而且如果按照一个特定的文件命名方案的规则(比如说,使用 11 | `/` 来分隔目录)用程序来构造文件名,那么你就会将代码捆绑到特定的文件系统上。 12 | 13 | To avoid this kind of nonportability, Common Lisp provides another 14 | representation of filenames: pathname objects. Pathnames represent 15 | filenames in a structured way that makes them easy to manipulate 16 | without tying them to a particular filename syntax. And the burden of 17 | translating back and forth between strings in the local syntax--called 18 | namestrings--and pathnames is placed on the Lisp implementation. 19 | 20 | 为了避免这种不可移植性,Common Lisp 21 | 提供了另一种文件名的表示方式:路径名(pathname)对象。路径名以一种结构化的方式来表示文件名,这种方式使得 22 | 它们易于管理而无须捆绑在特定的文件名语法上。而在以本地语法写成的字符串,即名字字符串(namestring),和路径名之间进行来回转换的责任则被放在了 23 | Lisp 实现身上。 24 | 25 | Unfortunately, as with many abstractions designed to hide the details 26 | of fundamentally different underlying systems, the pathname 27 | abstraction introduces its own complications. When pathnames were 28 | designed, the set of file systems in general use was quite a bit more 29 | variegated than those in common use today. Consequently, some nooks 30 | and crannies of the pathname abstraction make little sense if all 31 | you're concerned about is representing Unix or Windows 32 | filenames. However, once you understand which parts of the pathname 33 | abstraction you can ignore as artifacts of pathnames' evolutionary 34 | history, they do provide a convenient way to manipulate filenames. 35 | 36 | 不幸的是,如同许多被设计用于隐藏本质上不同的底层系统细节的抽象那样,路径名抽象也引入了它们自己的复杂性。当路径名最初被设计时,通常使用的文件系统集合比今天所使用的更加丰富多彩。这带来的结果是,在你只关心如何表示 37 | Unix 或 Windows 文件名时,路径名抽象的某些细微之处就没有什么意义了。不过,一旦你理解了路径名抽象中的哪些部分可以作为路径名发展史中的遗留产物而忽略时,你就会发现它们确实提供了一种管理文件名的便捷方式。 38 | 39 | Most places a filename is called for, you can use either a namestring 40 | or a pathname. Which to use depends mostly on where the name 41 | originated. Filenames provided by the user--for example, as arguments 42 | or as values in configuration files--will typically be namestrings, 43 | since the user knows what operating system they're running on and 44 | shouldn't be expected to care about the details of how Lisp represents 45 | filenames. But programmatically generated filenames will be pathnames 46 | because you can create them portably. A stream returned by OPEN also 47 | represents a filename, namely, the filename that was originally used 48 | to open the stream. Together these three types are collectively 49 | referred to as pathname designators. All the built-in functions that 50 | expect a filename argument accept all three types of pathname 51 | designator. For instance, all the places in the previous section where 52 | you used a string to represent a filename, you could also have passed 53 | a pathname object or a stream. 54 | 55 | 在多数使用文件名的调用场合里,你都可以同时使用个名字字符串或是路径名。具体使用哪一个在很大程度上取决于该名字的来源。由用户提供的文件名(例如作为参数或是配置文件中的值)通常是名字字符串,因为用户只知道它们所运行的文件系统而不关心 56 | Lisp 表示文件名的细节。但通过编程方法产生的文件名是路径名,因为你能可移植地创建它们。一个由 57 | **OPEN** 返回的流也代表文件名,也就是那个当初用来打开该流的文件名。这三种类型的文件名被总称为路径名描述符(pathname 58 | designator)。所有内置的以文件名作为参数的函数都能接受所有这三种路径名描述符。例如,前面章节里所有的用字符串来表示文件名的位置都同样可以传入路径名对象或流。 59 | 60 | -------------------------------------------------------------------------------- /docsrc/chap14/files-and-file-io.md: -------------------------------------------------------------------------------- 1 | # Files and File I/O(文件和文件 I/O) 2 | 3 | Common Lisp provides a rich library of functionality for dealing with 4 | files. In this chapter I'll focus on a few basic file-related tasks: 5 | reading and writing files and listing files in the file system. For 6 | these basic tasks, Common Lisp's I/O facilities are similar to those 7 | in other languages. Common Lisp provides a stream abstraction for 8 | reading and writing data and an abstraction, called pathnames, for 9 | manipulating filenames in an operating system-independent 10 | way. Additionally, Common Lisp provides other bits of functionality 11 | unique to Lisp such as the ability to read and write s-expressions. 12 | 13 | Common Lisp 14 | 提供了一个功能丰富用于处理文件的函数库。在本章里,我将把重点放在少数基本的文件相关任务上:读写文件以及列出文件系统中的文件。对这些基本任务,Common 15 | Lisp 的输入输出(I/O)机制与其他语言相似。Common 16 | Lisp 为读写数据提供了一个流的抽象和一个称为路径名(pathname)的抽象, 17 | 它们以一种与操作系统无关的方式来管理文件名。另外,Common Lisp 18 | 还提供了其他一些 Lisp 独有的功能,比如读写 S-表达式的能力。 19 | 20 | -------------------------------------------------------------------------------- /docsrc/chap14/how-we-got-here.md: -------------------------------------------------------------------------------- 1 | # How We Got Here(进化历程) 2 | 3 | The historical diversity of file systems in existence during the 70s 4 | and 80s can be easy to forget. Kent Pitman, one of the principal 5 | technical editors of the Common Lisp standard, described the situation 6 | once in `comp.lang.lisp` (Message-ID: `sfwzo74np6w.fsf@world.std.com`) 7 | thusly: 8 | 9 | 存在于上世纪 70 和 80 10 | 年代的文件系统的历史多样性很容易被遗忘。Kent Pitman,Common Lisp 11 | 标准的主要技术编辑之一,有一次在 `comp.lang.lisp` (Message-ID: `sfwzo74np6w.fsf@world.std.com`)新闻组上描述了如下情形: 12 | 13 | > The dominant file systems at the time the design [of Common Lisp] 14 | > was done were TOPS-10, TENEX, TOPS-20, VAX VMS, AT&T Unix, MIT 15 | > Multics, MIT ITS, not to mention a bunch of mainframe [OSs]. Some 16 | > were uppercase only, some mixed, some were case-sensitive but case- 17 | > translating (like CL). Some had dirs as files, some not. Some had 18 | > quote chars for funny file chars, some not. Some had wildcards, some 19 | > didn't. Some had `:up` in relative pathnames, some didn't. Some had 20 | > namable root dirs, some didn't. There were file systems with no 21 | > directories, file systems with non-hierarchical directories, file 22 | > systems with no file types, file systems with no versions, file 23 | > systems with no devices, and so on. 24 | 25 | > 在 Common Lisp 的设计完成时期,处于支配地位的文件系统是:TOPS-10、TENEX、TOPS-20、VAX VMS、AT&T Unix、MIT Multics、MIT 26 | > ITS,更不用说还有许多大型机操作系统了。它们中的一些只支持大写字母,一些是大小写混合的,另一些则是大小写敏感但却能自动作大小写转换(就像 27 | > Common Lisp)。它们中的一些将目录视为文件,而一些则不会。一些对于特殊的文件字符带有引用字符,另一些不会。一些带有通配符,而另一些没有。一些在相对路径名中使用 28 | > `:up`,另一些不这样做。一些带有可命名的根目录,而另一些没有。还存在没有目录的文件系统,使用非层次目录结构的文件系统,不支持文件类型的文件系统,没有版本的文件系统以及没有设备的文件系统,等等。 29 | 30 | If you look at the pathname abstraction from the point of view of any 31 | single file system, it seems baroque. However, if you take even two 32 | such similar file systems as Windows and Unix, you can already begin 33 | to see differences the pathname system can help abstract away--Windows 34 | filenames contain a drive letter, for instance, while Unix filenames 35 | don't. The other advantage of having the pathname abstraction designed 36 | to handle the wide variety of file systems that existed in the past is 37 | that it's more likely to be able to handle file systems that may exist 38 | in the future. If, say, versioning file systems come back into vogue, 39 | Common Lisp will be ready. 40 | 41 | 如果从任何单一文件系统的观点上观察路径名抽象,那么它看起来显得过于复杂。不过,如果考察两种像 42 | Windows 和 Unix 这样相似的文件系统,你可能已经开始注意路径名系统可能帮你抽象掉的一些区别了。例如,Windows 43 | 文件名含有一个驱动器字母,而 Unix 44 | 文件名却没有。使用这种用来处理过去存在的广泛的文件系统的路径名抽象带来的另一种好处是,它有可能可以处理将来可能存在的文件系统。比如说,如果版本文件系统重新流行起来的话,Common Lisp 就已经准备好了。 45 | 46 | -------------------------------------------------------------------------------- /docsrc/chap14/reading-binary-data.md: -------------------------------------------------------------------------------- 1 | # Reading Binary Data(读取二进制数据) 2 | 3 | By default **OPEN** returns character streams, which translate the 4 | underlying bytes to characters according to a particular 5 | character-encoding scheme. To read the raw bytes, you need to pass 6 | **OPEN** an `:element-type` argument of `'(unsigned-byte 8)`. You can pass 7 | the resulting stream to the function **READ-BYTE**, which will return an 8 | integer between 0 and 255 each time it's called. **READ-BYTE**, like the 9 | character-reading functions, also accepts optional arguments to 10 | specify whether it should signal an error if called at the end of the 11 | file and what value to return if not. In Chapter 24 you'll build a 12 | library that allows you to conveniently read structured binary data 13 | using **READ-BYTE**. 14 | 15 | 默认情况下,**OPEN** 16 | 返回字符流,它根据特定的字符编码方案将底层字节转化成字符。 为了读取原 17 | 始字节,你需要向 18 | **OPEN** 传递一个值为 `'(unsigned-byte 8)` 的 `:element-type` 19 | 参数。 你可以将得到的流传给 20 | **READ-BYTE**,它将在每次被调用时返回一个从 0 到 255 之间的整数。与字符读取函数一样,**READ-BYTE** 21 | 也支持可选的参数此便指定当其被调用在文件结尾时是否应该报错,以及在遇到结尾时返回何值。在第 22 | 24 章里你将构建一个库,它允许使用 **READ-BYTE** 23 | 来便利地读取结构化的二进制数据。 24 | 25 | -------------------------------------------------------------------------------- /docsrc/chap14/two-representations.md: -------------------------------------------------------------------------------- 1 | # Two Representations of Directory Names(目录名的两种表示方法) 2 | 3 | When dealing with pathnames that name directories, you need to be 4 | aware of one wrinkle. Pathnames separate the directory and name 5 | components, but Unix and Windows consider directories just another 6 | kind of file. Thus, on those systems, every directory has two 7 | different pathname representations. 8 | 9 | 当处理命名目录的路径名时,你需要注意一点。路径名将目录和名称组件区分开,但 10 | Unix 和 Windows 11 | 却将目录视为另一种类型的文件。这样,在这些系统里,每一个目录都有两种不同的路径名表示方法。 12 | 13 | One representation, which I'll call file form, treats a directory like 14 | any other file and puts the last element of the namestring into the 15 | name and type components. The other representation, directory form, 16 | places all the elements of the name in the directory component, 17 | leaving the name and type components **NIL**. If `/foo/bar/` is a directory, 18 | then both of the following pathnames name it. 19 | 20 | 一种表示方法,我将它称为文件形式(file 21 | form),将目录当成像其他任何文件一样来对待,将名字字符串中的最后一个元素放在名称和类型组件中。另一种表示方法,目录形式(directory 22 | form)将名字中的所有元素都放在目录组件中,而留下名称和类型组件为 23 | **NIL**。如果 `/foo/bar/` 是一个目录,那么下面两个路径名都可以命名它: 24 | 25 | ```lisp 26 | (make-pathname :directory '(:absolute "foo") :name "bar") ; file form 27 | (make-pathname :directory '(:absolute "foo" "bar")) ; directory form 28 | ``` 29 | 30 | When you create pathnames with **MAKE-PATHNAME**, you can control which 31 | form you get, but you need to be careful when dealing with 32 | namestrings. All current implementations create file form pathnames 33 | unless the namestring ends with a path separator. But you can't rely 34 | on user-supplied namestrings necessarily being in one form or 35 | another. For instance, suppose you've prompted the user for a 36 | directory to save a file in and they entered "/home/peter". If you 37 | pass that value as the `:defaults` argument of **MAKE-PATHNAME** like this: 38 | 39 | 当你用 **MAKE-PATHNAME** 40 | 创建路径名时,你可以控制得到的形式,但你需要在处理名字字符串时多加小心。当前所有实现都创建文件形式的路径名,除非名字字符串以一个路径分隔符结尾。但你不能依赖于用户提供的名字字符串必须是以一种或另一种形式。例如,当你提示用户输入一个用来保存文件的目录而它们输入了 41 | "/home/peter"。如果你像下面这样将该值作为 **MAKE-PATHNAME** 的 42 | `:defaults` 参数: 43 | 44 | ```lisp 45 | (make-pathname :name "foo" :type "txt" :defaults user-supplied-name) 46 | ``` 47 | 48 | you'll end up saving the file in `/home/foo.txt` rather than the 49 | intended `/home/peter/foo.txt` because the "peter" in the namestring 50 | will be placed in the name component when `user-supplied-name` is 51 | converted to a pathname. In the pathname portability library I'll 52 | discuss in the next chapter, you'll write a function called 53 | `pathname-as-directory` that converts a pathname to directory form. With 54 | that function you can reliably save the file in the directory 55 | indicated by the user. 56 | 57 | 那么最后你将把文件保存成 `/home/foo.txt` 58 | 而不是你想要的 `/home/peter/foo.txt`,因为当 `user-supplied-name` 59 | 被转化成一个路径名时,名字字符串中的 "peter" 60 | 将被放在名称组件中。在下一章我将讨论的路径名可移植库中,你将编写一个名为 61 | `pathname-as-directory` 62 | 的函数,它将一个路径名转化成目录形式。使用该函数,你可以放心地在用户给出的目录里保存文件。 63 | 64 | ```lisp 65 | (make-pathname 66 | :name "foo" :type "txt" :defaults (pathname-as-directory user-supplied-name)) 67 | ``` 68 | -------------------------------------------------------------------------------- /docsrc/chap15/a-portable-pathname-library.md: -------------------------------------------------------------------------------- 1 | # Practical: A Portable Pathname Library(实践:可移植路径名库) 2 | 3 | As I discussed in the previous chapter, Common Lisp provides an 4 | abstraction, the pathname, that's supposed to insulate you from the 5 | details of how different operating systems and file systems name 6 | files. Pathnames provide a useful API for manipulating names as names, 7 | but when it comes to the functions that actually interact with the 8 | file system, things get a bit hairy. 9 | 10 | 如同我在前面章节里讨论的那样,Common Lisp 11 | 提供了一种称为路径名的抽象,这样一来,你就不用顾忌不同操作系统和文件系统在文件命名方式上的差异。路径名提供了一个有用的 12 | API 来管理作为路径的名字,但是当它涉及实际与文件系统交互的函数时,事情就会变得有些复杂。 13 | 14 | The root of the problem, as I mentioned, is that the pathname 15 | abstraction was designed to represent filenames on a much wider 16 | variety of file systems than are commonly used now. Unfortunately, by 17 | making pathnames abstract enough to account for a wide variety of file 18 | systems, Common Lisp's designers left implementers with a fair number 19 | of choices to make about how exactly to map the pathname abstraction 20 | onto any particular file system. Consequently, different implementers, 21 | each implementing the pathname abstraction for the same file system, 22 | just by making different choices at a few key junctions, could end up 23 | with conforming implementations that nonetheless provide different 24 | behavior for several of the main pathname-related functions. 25 | 26 | 如同我提到的,问题的根源在于,路径名抽象被设计用来表示比当今常用的文件系统更加广泛的系统上的文件名。不幸的是,为了让路径名足够抽象从而可以应用于广泛的文件系统,Common 27 | Lisp 28 | 的设计者们留给了实现者们大量的选择空间,来决定究竟如何将路径名抽象映射到任何特定文件系统上。这样带来的结果是,不同的实现者虽然在相同的文件系统上实现路径名抽象,但在一些关键点上却做出了不同的选择,从而导致遵循标准的实现在一些主要的路径名相关函数上不可避免地提供了不同的行为。 29 | 30 | However, one way or another, all implementations provide the same 31 | basic functionality, so it's not too hard to write a library that 32 | provides a consistent interface for the most common operations across 33 | different implementations. That's your task for this chapter. In 34 | addition to giving you several useful functions that you'll use in 35 | future chapters, writing this library will give you a chance to learn 36 | how to write code that deals with differences between 37 | 38 | 然而,所有的实现都以这样或那样的方式提供了相同的基本功能,因此你可以写一个库,对多数跨越不同实现的常见操作提供一致的接口。这就是你在本章中的任务。编写这个库,你不但可以获得后续几章将会用到的几个有用的函数,还可以有机会学习如何编写处理不同 39 | Lisp 实现间有区别的代码。 40 | -------------------------------------------------------------------------------- /docsrc/chap15/chap15.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docsrc/chap15/packing-the-library.md: -------------------------------------------------------------------------------- 1 | # Packaging the Library(给库打包) 2 | 3 | Speaking of packages, if you download the complete code for this 4 | library, you'll see that it's defined in a new package, 5 | `com.gigamonkeys.pathnames`. I'll discuss the details of defining and 6 | using packages in Chapter 21. For now you should note that some 7 | implementations provide their own packages that contain functions with 8 | some of the same names as the ones you'll define in this chapter and 9 | make those names available in the `CL-USER` package. Thus, if you try to 10 | define the functions from this library while in the `CL-USER` package, 11 | you may get errors or warnings about clobbering existing 12 | definitions. To avoid this possibility, you can create a file called 13 | `packages.lisp` with the following contents: 14 | 15 | 从包的角度讲,如果你下载了该库的完整代码,会看到它被定义在一个新的包 16 | `com.gigamonkeys.pathnames` 中。我将在第 21 17 | 章讨论定义使用包的细节。目前你应当注意,某些实现提供了它们自己的包,其中含有一些函数与你将在本章中定义的一些函数有相同的名字,并且这些名字可在 18 | `CL-USER` 包中访问。这样,如果你试图在 `CL-USER` 19 | 包中定义该库中的某些函数,可能会得到关于破坏了已有定义的错误或警告。为了避免发生这种情况,你可以创建一个称为 20 | `packages.lisp` 的文件,其中带有下面的内容: 21 | 22 | ```lisp 23 | (in-package :cl-user) 24 | 25 | (defpackage :com.gigamonkeys.pathnames 26 | (:use :common-lisp) 27 | (:export 28 | :list-directory 29 | :file-exists-p 30 | :directory-pathname-p 31 | :file-pathname-p 32 | :pathname-as-directory 33 | :pathname-as-file 34 | :walk-directory 35 | :directory-p 36 | :file-p)) 37 | ``` 38 | 39 | and **LOAD** it. Then at the REPL or at the top of the file where you type 40 | the definitions from this chapter, type the following expression: 41 | 42 | 并加载它,然后在 REPL 中或者在你输入定义的文件顶端,输入下列表达式: 43 | 44 | ```lisp 45 | (in-package :com.gigamonkeys.pathnames) 46 | ``` 47 | 48 | In addition to avoiding name conflicts with symbols already available 49 | in `CL-USER`, packaging the library this way also makes it easier to use 50 | in other code, as you'll see in several future chapters. 51 | 52 | 将库以这种方形式打包,除了可以避免与那些已存在于 `CL-USER` 53 | 包中的符号产生冲突以外,还可以使其更容易被其他代码使用,你在后续几章中将看到这一点。 54 | -------------------------------------------------------------------------------- /docsrc/chap15/the-api.md: -------------------------------------------------------------------------------- 1 | # The API(应用程序接口) 2 | 3 | The basic operations the library will support will be getting a list 4 | of files in a directory and determining whether a file or directory 5 | with a given name exists. You'll also write a function for recursively 6 | walking a directory hierarchy, calling a given function for each 7 | pathname in the tree. 8 | 9 | 该库支持的基本操作是获取目录中的文件列表,并检测给定名字的文件或目录是否存在。你也将编写函数用于递归遍历目录层次,并在目录树的每个路径名上调用给定的函数。 10 | 11 | In theory, these directory listing and file existence operations are 12 | already provided by the standard functions **DIRECTORY** and 13 | **PROBE-FILE**. However, as you'll see, there are enough different ways to 14 | implement these functions--all within the bounds of valid 15 | interpretations of the language standard--that you'll want to write 16 | new functions that provide a consistent behavior across 17 | implementations. 18 | 19 | 从理论上来讲,这些列目录和测试文件存在性的操作已经由标准函数 **DIRECTORY** 20 | 和 **PROBE-FILE** 21 | 提供了。不过正如你将看到的那样,会有许多不同的方式来实现这些函数──所有这些均属于语言标准的有效解释,因此你希望编写新的函数,以在不同实现间提供一致的行为。 22 | -------------------------------------------------------------------------------- /docsrc/chap15/walking-a-directory-tree.md: -------------------------------------------------------------------------------- 1 | # Walking a Directory Tree(遍历一个目录树) 2 | 3 | Finally, to round out this library, you can implement a function 4 | called `walk-directory`. Unlike the functions defined previously, this 5 | function doesn't need to do much of anything to smooth over 6 | implementation differences; it just needs to use the functions you've 7 | already defined. However, it's quite handy, and you'll use it several 8 | times in subsequent chapters. It will take the name of a directory and 9 | a function and call the function on the pathnames of all the files 10 | under the directory, recursively. It will also take two keyword 11 | arguments: `:directories` and `:test`. When `:directories` is true, it will 12 | call the function on the pathnames of directories as well as regular 13 | files. The `:test` argument, if provided, specifies another function 14 | that's invoked on each pathname before the main function is; the main 15 | function will be called only if the test function returns true. 16 | 17 | 最后,为了完成这个库,你可以实现一个称为 `walk-directory` 18 | 的函数。与前面定义的那些函数不同,这个函数不需要做任何事情来消除实现间的区别,它只需要用到你已经定义的那些函数。尽管如此,该函数很有用,你将在后续几章里多次用到它。它接受一个目录的名字和一个函数,并在该目录下所有文件的路径名上递归地调用该函数。它还接受两个关键字参数: 19 | `:directories` 20 | 和 `:test`。当 `:directories` 21 | 为真时,它将在所有目录的路径名和正规文件上调用该函数。如果有 `:test` 22 | 参数,它指定另一个函数,在调用主函数之前在每一个路径名上调用该函数,主 23 | 函数只有当测试参数返回真时才会被调用。`:test` 24 | 参数的默认值是一个总是返回真的函数,它是通过调用标准函数 25 | **CONSTANTLY** 而生成的。 26 | 27 | ```lisp 28 | (defun walk-directory (dirname fn &key directories (test (constantly t))) 29 | (labels 30 | ((walk (name) 31 | (cond 32 | ((directory-pathname-p name) 33 | (when (and directories (funcall test name)) 34 | (funcall fn name)) 35 | (dolist (x (list-directory name)) (walk x))) 36 | ((funcall test name) (funcall fn name))))) 37 | (walk (pathname-as-directory dirname)))) 38 | ``` 39 | 40 | Now you have a useful library of functions for dealing with 41 | pathnames. As I mentioned, these functions will come in handy in later 42 | chapters, particularly Chapters 23 and 27, where you'll use 43 | walk-directory to crawl through directory trees containing spam 44 | messages and MP3 files. But before we get to that, though, I need to 45 | talk about object orientation, the topic of the next two chapters. 46 | 47 | 现在你有了一个用于处理路径名的有用的函数库。正如我提到的那样,这些函数在后面的章节里将会很有用,尤其是第 48 | 23 章和第 27 章,在那里你将使用 `walk-directory` 在混合了垃圾信息和 49 | MP3 文件的目录树中艰难前行,但在我们到达那里之前,我还需要在接下来的两章中谈论一下面向对象。 50 | -------------------------------------------------------------------------------- /docsrc/chap16/account-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe/pcl-cn/19aa62184a1b5ca0890114cfd9f468d74a0a207f/docsrc/chap16/account-hierarchy.png -------------------------------------------------------------------------------- /docsrc/chap16/chap16.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docsrc/chap16/defgeneric.md: -------------------------------------------------------------------------------- 1 | # DEFGENERIC(定义广义函数) 2 | 3 | To give you a feel for these macros and the various facilities they 4 | support, I'll show you some code you might write as part of a banking 5 | application--or, rather, a toy banking application; the point is to 6 | look at a few language features, not to learn how to really write 7 | banking software. For instance, this code doesn't even pretend to deal 8 | with such issues as multiple currencies let alone audit trails and 9 | transactional integrity. 10 | 11 | 为了给你一个关于这些宏和它们所支持的不同功能的大致印象,我将向你展示一些可能作为一个银行应用,或者说,一个相当幼稚的银行应用的一部分来编写的代码。重点在于观察一些语言特性而不是学习如何实际编写银行软件。例如,这些代码甚至并不打算处理像多种货币、审查跟踪以及事务集成这样的问题。 12 | 13 | Because I'm not going to discuss how to define new classes until the 14 | next chapter, for now you can just assume that certain classes already 15 | exist: for starters, assume there's a class `bank-account` and that it 16 | has two subclasses, `checking-account` and `savings-account`. The class 17 | hierarchy looks like this: 18 | 19 | 由于我不准备在下一章之前讨论如何定义新的类,因此目前你可以假设特定的类已经存在了。假设你已有一个 20 | `bank-account` 类以及它的两个子类 `checking-account` 和 21 | `savings-account`。类层次关系如下所示: 22 | 23 | ![account hierarchy](account-hierarchy.png) 24 | 25 | The first generic function will be `withdraw`, which decreases the 26 | account balance by a specified amount. If the balance is less than the 27 | amount, it should signal an error and leave the balance unchanged. You 28 | can start by defining the generic function with **DEFGENERIC**. 29 | 30 | 第一个广义函数将是 31 | `withdraw`,它将帐户余额减少指定数量。如果余额小于提款量,它将报错并保持余额不变。你可以从通过 32 | **DEFGENERIC** 定义该广义函数开始。 33 | 34 | The basic form of **DEFGENERIC** is similar to **DEFUN** except with no 35 | body. The parameter list of **DEFGENERIC** specifies the parameters that 36 | must be accepted by all the methods that will be defined on the 37 | generic function. In the place of the body, a **DEFGENERIC** can contain 38 | various options. One option you should always include is 39 | `:documentation`, which you use to provide a string describing the 40 | purpose of the generic function. Because a generic function is purely 41 | abstract, it's important to be clear to both users and implementers 42 | what it's for. Thus, you might define `withdraw` like this: 43 | 44 | **DEFGENERIC** 的基本形式与 **DEFUN** 45 | 相似,只是没有函数体。**DEFGENERIC** 46 | 的形参列表指定了那些定义在该广义函数上的所有方法都必须接受的参数。在函数体的位置上, **DEFGENERIC** 47 | 可能含有不同的选项。一个你应当总是带有的选项是 48 | `:documentation`,它提供了一个用来描述该广义函数用途的字符串。由于广义函数是纯抽象的,让用户和实现者了解它的用途将是重要的。因此,你可以像下面这样定义 `withdraw`: 49 | 50 | ```lisp 51 | (defgeneric withdraw (account amount) 52 | (:documentation "Withdraw the specified amount from the account. 53 | Signal an error if the current balance is less than amount.")) 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /docsrc/chap16/object-reorientation.md: -------------------------------------------------------------------------------- 1 | # Object Reorientation: Generic Functions(重新审视面向对象:广义函数) 2 | 3 | Because the invention of Lisp predated the rise of object-oriented 4 | programming by a couple decades, new Lispers are sometimes surprised 5 | to discover what a thoroughly object-oriented language Common Lisp 6 | is. Common Lisp's immediate predecessors were developed at a time when 7 | object orientation was an exciting new idea and there were many 8 | experiments with ways to incorporate the ideas of object orientation, 9 | especially as manifested in Smalltalk, into Lisp. As part of the 10 | Common Lisp standardization, a synthesis of several of these 11 | experiments emerged under the name Common Lisp Object System, or 12 | CLOS. The ANSI standard incorporated CLOS into the language, so it no 13 | longer really makes sense to speak of CLOS as a separate entity. 14 | 15 | Lisp 的发明比面向对象编程的兴起早了几十年。新的 Lisp 16 | 程序员们有时会惊奇地发现,原来 Common Lisp 17 | 竟是一门非常彻底的面向对象语言。Common Lisp 之前的几个 Lisp 18 | 方言开发于面向对象还是一个崭新思想的年代,而那时有许多实验在探索将面向对象的思想(尤其是 19 | Smalltalk 中所展现的形式)合并到 Lisp 中的方式。作为 Common Lisp 20 | 标准化过程的一部分,这些实验中的一些被合成在一起,以 Common Lisp 21 | Object System (CLOS) 的名义出现。ANSI CL 标准将 CLOS 22 | 合并到了语言之中,因此单独提及 CLOS 就不再有任何实际意义了。 23 | 24 | The features CLOS contributed to Common Lisp range from those that can 25 | hardly be avoided to relatively esoteric manifestations of Lisp's 26 | language-as-language-building-tool philosophy. Complete coverage of 27 | all these features is beyond the scope of this book, but in this 28 | chapter and the next I'll describe the bread-and-butter features and 29 | give an overview of Common Lisp's approach to objects. 30 | 31 | CLOS 为 Common Lisp 32 | 贡献的那些特性里既有必不可少的,也有相对难懂的 Lisp 33 | “语言作为语言的构造工具” 34 | 这一哲学的具体表现。本书无法对所有这些特性全部加以介绍,但在本章和下一章里,我将描述其中最常用的特性,并给出关于 35 | Common Lisp 对象的概述。 36 | 37 | You should note at the outset that Common Lisp's object system offers 38 | a fairly different embodiment of the principles of object orientation 39 | than many other languages. If you have a deep understanding of the 40 | fundamental ideas behind object orientation, you'll likely appreciate 41 | the particularly powerful and general way Common Lisp manifests those 42 | ideas. On the other hand, if your experience with object orientation 43 | has been largely with a single language, you may find Common Lisp's 44 | approach somewhat foreign; you should try to avoid assuming that 45 | there's only one way for a language to support object orientation.2 If 46 | you have little object-oriented programming experience, you should 47 | have no trouble understanding the explanations here, though it may 48 | help to ignore the occasional comparisons to the way other languages 49 | do things. 50 | 51 | 你应当从一开始就注意到,Common Lisp 52 | 的对象系统体现了与许多其他语言相当不同的面向对象的原则。如果你能够深刻理解面向对象背后的基本思想,那么将会感谢 53 | Common Lisp 54 | 在实现这些思想时所采用的强大和通用的方式。另一方面,如果你的面向对象经历很大程度上来自单一语言,那么你可能会发现 55 | Common Lisp 56 | 的观点多少有些另类。你应当试图避免假设只存在一种方式令一门语言支持面向对象。 如果你几乎没有面向对象编程经验,那么也应当不难理解这里的解释,不过文中偶尔比较其他语言做同样事情的方式的内容,你就只好跳过不看了。 57 | -------------------------------------------------------------------------------- /docsrc/pdf.properties: -------------------------------------------------------------------------------- 1 | args.chapter.layout = BASIC 2 | --------------------------------------------------------------------------------