Lisp 整体思想
形式(Form)#
人可以通过实践来学习一件事,这对于 Lisp 来说特别有效,因为 Lisp 是一门交互式的语言。任何 Lisp 系统都含有一个交互式的前端,叫做顶层 (toplevel)。你在顶层输入 Lisp 表达式,而系统会显示它们的值。
许多 Common Lisp 的实现用 >
作为顶层提示符。
$ clisp
> 1
1
>
打印的值与输入的值相同,数字 1 称之为对自身求值。
> (+ 2 3)
5
在表达式 (+ 2 3) 里, +
称为操作符,而数字 2
跟 3
称为实参。
在 Lisp 里,把 +
操作符写在前面,接着写实参,再把整个表达式用一对括号包起来。
这称为 「前序表达式」。
日常生活的表示法,要写两次 +
号 2 + 3 + 4
,在 Lisp 里,只需要增加一个实参:(+ 2 3 4)
。
日常生活中用 +
时,它必须有两个实参,一个在左,一个在右。前序表示法的灵活性代表着,在 Lisp 里, +
可以接受任意数量的实参,包含了没有实参:
> (+)
0
> (+ 2)
2
> (+ 2 3)
5
> (+ 2 3 4)
9
> (+ 2 3 4 5)
14
由于操作符可接受不定数量的实参,我们需要用括号来标明表达式的开始与结束。
表达式可以嵌套。即表达式里的实参,可以是另一个复杂的表达式:
> (/ (- 7 1) (- 4 2))
3
所有的 Lisp 表达式,要么是 1 这样的数原子,要么是包在括号里,由零个或多个表达式所构成的列表。以下是合法的 Lisp 表达式:
2 (+ 2 3) (+ 2 3 4) (/ (- 7 1) (- 4 2))
所有的 Lisp 程序都采用这种形式。而像是 C 这种语言,有着更复杂的语法:算术表达式采用中序表示法;函数调用采用某种前序表示法,实参用逗号隔开;表达式用分号隔开;而一段程序用大括号隔开。
求值(Evaluation)#
在 Lisp 里, +
是函数,然而如 (+ 2 3)
的表达式,是函数调用。
当 Lisp 对函数调用求值时,它做下列两个步骤:
- 首先从左至右对实参求值。在这个例子当中,实参对自身求值,所以实参的值分别是
2
跟3
。 - 实参的值传入以操作符命名的函数。在这个例子当中,将
2
跟3
传给+
函数,返回5
。
如果实参本身是函数调用的话,上述规则同样适用。以下是当 (/ (- 7 1) (- 4 2))
表达式被求值时的情形:
- Lisp 对
(- 7 1)
求值:7
求值为7
,1
求值为1
,它们被传给函数-
,返回6
。 - Lisp 对
(- 4 2)
求值:4
求值为4
,2
求值为2
,它们被传给函数-
,返回2
。 - 数值
6
与2
被传入函数/
,返回3
。
但不是所有的 Common Lisp 操作符都是函数,不过大部分是。函数调用都是这么求值。由左至右对实参求值,将它们的数值传入函数,来返回整个表达式的值。这称为 Common Lisp 的求值规则
。
如果输入 Lisp 不能理解的东西,它会打印一个错误讯息,接着带你到一种叫做中断循环(break loop)的顶层。输入 :abort
跳出:
> (/ 1 0)
Error: Division by zero
Options: :abort, :backtrace
>> :abort
>
一个不遵守 Common Lisp 求值规则的操作符是 quote
。 quote
是一个特殊的操作符,意味着它自己有一套特别的求值规则。这个规则就是:什么也不做。quote
操作符接受一个实参,并完封不动地返回它。
> (quote (+ 3 5))
(+ 3 5)
使用缩写 '
比使用整个 quote
表达式更常见。
Lisp 提供 quote
作为一种保护表达式不被求值的方式。
数据(Data)#
Lisp 提供了所有在其他语言找的到的,以及其他语言所找不到的数据类型。一个我们已经使用过的类型是整数(integer),整数用一系列的数字来表示,比如: 256
。另一个 Common Lisp 与多数语言有关,并很常见的数据类型是字符串(string),字符串用一系列被双引号包住的字符串表示,比如: "ora et labora"
。整数与字符串一样,都是对自身求值的。
有两个通常在别的语言所找不到的 Lisp 数据类型是符号(symbol)与列表(lists),符号是英语的单词 (words)。无论你怎么输入,通常会被转换为大写:
> 'Artichoke
ARTICHOKE
列表是由被括号包住的零个或多个元素来表示。元素可以是任何类型,包含列表本身。使用列表必须要引用,不然 Lisp 会以为这是个函数调用:
> '(my 3 "Sons")
(MY 3 "Sons")
> '(the list (a b c) has 3 elements)
(THE LIST (A B C) HAS 3 ELEMENTS)
注意引号保护了整个表达式(包含内部的子表达式)被求值。
你可以调用 list
来创建列表。由于 list
是函数,所以它的实参会被求值。这里我们看一个在函数 list
调用里面,调用 +
函数的例子:
> (list 'my (+ 2 1) "Sons")
(MY 3 "Sons")
为什么我们需要 quote
,如果一个列表被引用了,则求值规则对列表自身来求值;如果没有被引用,则列表被视为是代码,依求值规则对列表求值后,返回它的值。
> (list '(+ 2 1) (+ 2 1))
((+ 2 1) 3)
在 Common Lisp 里有两种方法来表示空列表:
> ()
NIL
> nil
NIL
列表操作(List Operations)#
用函数 cons
来构造列表。如果传入的第二个实参是列表,则返回由两个实参所构成的新列表,新列表为第一个实参加上第二个实参:
> (cons 'a '(b c d))
(A B C D)
可以通过把新元素建立在空表之上,来构造一个新列表。list
,不过就是一个把几个元素加到 nil
上的快捷方式:
> (cons 'a (cons 'b nil))
(A B)
> (list 'a 'b)
(A B)
取出列表元素的基本函数是 car
和 cdr
。对列表取 car
返回第一个元素,而对列表取 cdr
返回第一个元素之后的所有元素:
> (car '(a b c))
A
> (cdr '(a b c))
(B C)
> (car (cdr (cdr '(a b c d))))
C
> (third '(a b c d))
C
真假(Truth)#
在 Common Lisp 里,符号 t
是表示逻辑 真
的缺省值。与 nil
相同, t
也是对自身求值的。如果实参是一个列表,则函数 listp
返回 真
:
> (listp '(a b c))
T
函数的返回值将会被解释成逻辑 真
或逻辑 假
时,则称此函数为谓词(predicate)。在 Common Lisp 里,谓词的名字通常以 p
结尾。
逻辑 假
在 Common Lisp 里,用 nil
,即空表来表示。如果我们传给 listp
的实参不是列表,则返回 nil
。
> (listp 27)
NIL
由于 nil
在 Common Lisp 里扮演两个角色,如果实参是一个空表,则函数 null
返回 真
。
> (null nil)
T
而如果实参是逻辑 假
,则函数 not
返回 真 :
> (not nil)
T
null
与 not
做的是一样的事情。
在 Common Lisp 里,最简单的条件式是 if
。通常接受三个实参:一个 test 表达式,一个 then 表达式和一个 else 表达式。若 test
表达式求值为逻辑 真
,则对 then
表达式求值,并返回这个值。若 test
表达式求值为逻辑 假
,则对 else
表达式求值,并返回这个值:
> (if (listp '(a b c))
(+ 1 2)
(+ 5 6))
3
> (if (listp 27)
(+ 1 2)
(+ 5 6))
11
与 quote
相同,if
是特殊的操作符。不能用函数来实现,因为实参在函数调用时永远会被求值,而 if
的特点是,只有最后两个实参的其中一个会被求值。if
的最后一个实参是选择性的。如果忽略它的话,缺省值是 nil
:
> (if (listp 27)
(+ 1 2))
NIL
虽然 t
是逻辑 真
的缺省表示法,任何非 nil
的东西,在逻辑的上下文里通通被视为 真
。
> (if 27 1 2)
1
逻辑操作符 and
和 or
与条件式类似。两者都接受任意数量的实参,但仅对能影响返回值的几个实参求值。如果所有的实参都为 真
(即非 nil
),那么 and
会返回最后一个实参的值:
> (and t (+ 1 2))
3
如果其中一个实参为 假
,那之后的所有实参都不会被求值。 or
也是如此,只要碰到一个为 真
的实参,就停止对之后所有的实参求值。
这两个操作符称为宏。宏和特殊的操作符一样,可以绕过一般的求值规则。
本作品采用《CC 协议》,转载必须注明作者和本文链接