Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:# ` K8 L R" b$ h* u
# o3 b& M2 l) d. S
既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述? k# H4 R& G( ?6 ^
在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数?
' Y5 Y4 Q8 ]. I! V(注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)9 i( w- C4 O8 z2 c
8 o4 A. b6 N( Y4 }; [- y* f0 V! l1 p这一集中,我们将描述几个常用的函数,并给出它们的简单实现/ p/ a( G: U' @* `
/ a$ }, V' F# D
首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?/ H' u# B0 ?) u, Q& }7 D
7 J4 ?3 z0 `2 ]+ b
可能有些读者已经想到了,取第二个元素可以采用如下形式:6 k; H# b0 a/ b
+ ]" W" T7 j: G( I
(car (cdr x))
# W, I1 o) i$ o, s8 o2 ]* }! T. J3 ~- W9 {$ j1 S0 a
同理,取第三个元素是这样的:
% y6 C, H. T2 |* T: ~0 w& H
. s9 ~$ n( z% ](car (cdr (cdr x)))
' C. d* C" y7 Q5 X6 P- K8 R$ _: a, C1 S2 g* t
事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:
$ h F. M4 |0 L. j* f
. l8 }8 J3 \. a6 @1 V% n. T$ U> (cadr '((a b) (c d) e))
+ `1 f9 n& {. ~( k(c d)
. _, T7 `+ D8 O: Y& ] w. ]> (caddr '((a b) (c d) e))5 d$ ?3 x: h* X0 c) n
e- U. j; Q, h) ?
> (cdar '((a b) (c d) e))7 [: M, P" J% g8 l; G
(b)& I; C* X' O9 H6 Q* p8 l/ R
8 i( Z5 t9 _! ?( I5 d6 {9 Z4 i4 ]另外,使用(list e1 e2 ... en)来表示
" m/ R4 j6 q# T# v3 I( J6 l(cons e1 (cons e2 (... (cons en '())...)))
- v, E% L8 c: R- i" C
, b1 Z0 ]4 _8 |3 o> (cons 'a (cons 'b (cons 'c '())))6 [' c) k. u" |, V, ?! ^( y
(a b c)
0 c" h7 U: n, h& Q3 g: T# S> (list 'a 'b 'c)5 V5 d4 g0 Z* N/ e1 i' U! K; s0 g
(a b c)% c' t6 L5 E' B1 ~) N0 d
9 v$ H; Q% v z, D
现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。
3 V1 z, ~$ N+ l* {1 A* Y" m6 t
4 e; Y2 Z; T1 u7 Y1 k(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字)
4 A3 l4 v% A# I( t+ t/ ~6 g+ s. K
. G" C* i5 I) ?4 o(null x),测试x是否为空表。例如:
: u' |/ w& |3 A% Q( ]' R> (null 'a)
1 n+ p K3 S0 k() y8 W p( b% s! R# z* G
> (null '())) M9 j7 C; D/ a, [% O
t
) k8 i" J% S9 ]# D# Z2 W5 f1 x5 D(and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。
j+ n) _2 ]' o; v$ T7 \: o> (and 'a 'b)
/ S- A0 r2 u' f( [3 Ht8 ~7 _2 {1 z, b) u
> (and (atom 'a) (eq 'b 'c))
* J: [% Y, u) F()
: L6 N; M5 ^3 d( ?(not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:
6 d7 x' g& M8 w2 @# i6 M> (not 'a)
/ B% P# g) v2 A" \* k0 V. g()5 v! k8 k O3 O7 X6 v
> (not (eq 'a 'b))
% ]2 O# Q4 u( K) Bt ( T% ? N7 V7 K+ b4 I" P+ ^
(append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:
3 G( A7 T$ H& m* B- K$ i> (append '(a b) '(c d)); n7 r1 J0 H7 ?6 @
(a b c d)
7 T/ F$ _9 T0 r* D; c> (append '() '(x y))
5 N' V. Z3 F! u(x y) . b. c w! V* k' u$ d, K3 J1 U
(pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:! K8 z1 Z, v1 e: x. O/ n: M
> (pair '(a b c) '(x y z))
, ^* m% A) R5 |((a x) (b y) (c z)) 1 g$ Y% O+ ?& b5 k! J
(assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:( X1 ~. Y7 Y) I
> (assoc 'a '((a x) (b y)))( F9 g( J D. B8 I C2 j
x# o; j, u* B$ s/ H2 N( l
> (assoc 'a '((a (foo bar)) (b y) (c z)))
- \% n* s+ @& u( Z& R! G(foo bar) / u/ w& K/ q, ]1 o! ^
(subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:& C" A# K8 z! L1 k
> (subst '(x y) 'b '(a b (a b c) d))
, o" A @9 f0 |" w/ J T(a (x y) (a (x y) c) d)' K* g1 _, ?7 i/ Y0 E7 p7 U" B+ \# h
下面我们给出这些常用函数的简单实现:
% e3 ~* Q$ `& w" Y& t# o3 e4 E C0 U0 ^7 Z& o) d- B$ Z
(defun null (x)0 P7 R- p2 t' B. w
(eq x '())) ! \. |/ e! n; T' D' A
(defun and (x y)
: k3 [# h% g) [/ \6 V3 @ (cond (x (cond (y 't) ('t '())))
. e" ]* e4 h |" S# e ('t '()))) . i+ |$ K& J: \
(defun not (x). y# E1 x! n5 ]9 C* I" @1 i) |
(cond (x '())6 l8 w8 K2 k7 T$ c( F8 U) Q7 x5 E
('t 't))) : k; T! O6 f5 h5 R0 ^/ O# j0 ]
(defun append (x y); |1 g: e+ Q# v. N5 q
(cond ((null x) y)
3 Q2 v$ R0 }; W7 G( q$ L& d$ o6 w ('t (cons (car x) (append (cdr x) y))))) 1 A% \7 s5 |, K% H+ X" ]( Q
(defun pair (x y)3 i$ {$ O7 n8 X' q! I5 Q
(cond ((and (null x) (null y)) '())% C- f3 x! @) ^. L
((and (not (atom x)) (not (atom y))), T8 c, q1 a% n. T | a: m" x
(cons (list (car x) (car y))
! u5 ^& Q3 H( Z/ E6 v! I (pair (cdr) (cdr y))))))
! Z, t( d" u0 H(defun assoc (x y)
. m3 l' _0 |& }* }7 j9 Q% Y7 M) s (cond ((eq (caar y) x) (cadar y))
$ |0 z7 r. S! _0 m: D# W ('t (assoc x (cdr y)))))
/ y: Q& K9 f* o2 T& G(defun subst (x y z)
1 }( M* I6 E0 v. @ (cond ((atom z)4 A' C. D; |6 z: I6 {' T
(cond ((eq z y) x). C; K8 G$ r" P+ Q/ h' P# c9 c
('t z)))
! ]0 g2 ]- q+ f0 c7 r ('t (cons (subst x y (car z))
+ T7 o4 M4 a' L/ } \8 |+ ?8 Z (subst x y (cdr z))))))
/ X! i/ X- m3 r4 h' U如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。
4 Z& a( F4 W) U. X7 j4 d }( }- c+ ^
9 X9 U5 E+ ~8 f这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。4 k" {4 O5 i+ w& y
5 {& l9 c9 q# T$ h5 N理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。 |