Common Lisp 允许程序员编写会被计算机忽略的“注释”。
单行注释以一个或多个分号(;)开头,偶尔你还会看到下面这种写法:
(code...) ; => value
这里的注释用来指出 Common Lisp 运行该行代码后返回的值。
按照惯用风格,行末的简短注释用一个分号,代码段上方的较长注释用两个,描述函数之类内容的注释用三个,而源文件顶部的文件头之类的注释则用四个。
Common Lisp 中的所有对象要么是“原子”(单个、不可分割的值),要么是“cons”(由构造函数 "cons" 生成)。
一个 cons 由两部分组成:第一个元素和其余的元素。
出于历史原因,这两部分分别叫做car和cdr。
当这些 cons 作为代码被求值时,第一个元素(car)表示被调用的函数,而其余的元素(cdr)则表示传给该函数的实参:
(<function> <arg1> <arg2> ... <argN>)
; ^ car ^ | ^ cdr ^
所有 Common Lisp 代码都由 S-表达式(Symbolic Expression,符号表达式)构成,简称 sexpr。每个 sexpr 要么是原子,要么是 cons。当 S-表达式被求值时,会自动返回某个值,这个值会取代该表达式。当你自己编写函数时(使用defun),defun函数体中的最后一个值会被自动返回:
;; Defining a new function
(defun gimme-foo () 'foo)
;; Calling the function as an S-Expression
(gimme-foo) ; => FOO
Common Lisp 中的符号是一种特殊的值,可以指向其他值;而对于_关键字_来说,则指向自身。 当 Lisp 对符号求值时,会用它们所指向的值来替换它们:
foo ; => <whatever-foo-points-to>
:foo ; => :FOO
注意,关键字以开头的冒号(:)表示。
引用,也就是在 S-表达式前面加上',会告诉 Lisp 不要对该表达式求值。
在我们的defun示例中引用'foo,就避免了让 Lisp 去查找(并且查找不到)FOO本应指向的东西,而是直接返回FOO这个值本身。
如果FOO在我们的程序中没有在任何地方定义:
foo ; => <ERROR! Lisp doesn't know what foo points to!>
'foo ; => FOO
目前,你可以先把它当作一种从函数中返回符号的方式,但之后我们会重新回顾引用,并在今后的概念练习中进一步探讨它的影响。
你!对,就是你,勇敢的 Lisp 程序员!Lisp 外星人 Lenny 需要你的帮助!Lenny 想和其他 Lisp 外星人一起出去玩,可他们的父母说了,房间不收拾干净,哪儿也别想去。
问题是,Lenny 几乎看不见地板!Lenny 分不清原子和 cons,分不清符号和关键字,分不清car和cdr!
你能帮 Lenny 把这团乱麻理清吗?别忘了你的括号,你会用得上的!
Lenny 动工之前,得先复习一下符号和关键字长什么样。毕竟他们已经很久没收拾过房间了……帮帮 Lenny,定义两个函数:lennys-favorite-food 求值为某个符号,lennys-secret-keyword 求值为某个关键字。例如:
(lennys-favorite-food) ; => LASAGNA
(lennys-secret-keyword) ; => :ALIENS-ARE-REAL
注意,你可以返回任何你喜欢的符号或关键字(如果你不喜欢千层面,或者不相信外星人)。
Lenny 复习完符号和关键字,就该动手收拾了!要理清房间里散落一地的 S-表达式,Lenny 需要有人帮忙判断什么是 cons、什么是原子。你可以实现两个函数来帮忙:is-an-atom-p 和 is-a-cons-p:
(is-an-atom-p 'one-thing) ; => T
(is-an-atom-p '(two things)) ; => NIL
(is-a-cons-p 'one-thing) ; => NIL
(is-a-cons-p '(two things)) ; => T
这一节里,有几个内置的 Lisp 函数可能会派上用场。另外,现在你可以直接认为 T 为真、NIL 为假。
最后,Lenny 的 cons 太占地方,没法整齐地收好,得把它们拆成更小的部分。你能再定义两个函数来拆分这些 cons 吗(first-thing 和 rest-of-it)?
(first-thing '(first second third)) ; => FIRST
(rest-of-it '(first second third)) ; => (SECOND THIRD)
同样,Common Lisp 的一些内置函数在这里可能会派上用场。