Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:
$ a$ }/ J$ l1 w; l9 X
5 z6 a5 g6 D' v @+ P- o, F: P既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述? , N4 v' p: K7 f2 Z$ n6 L5 v% ]
在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数?
5 b- Y9 u- ]$ I5 E; n# F6 ^& m+ U(注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)
3 y1 d2 u2 j( e% ?; W, _6 _
' X) E1 W5 K* [7 ^5 f8 j这一集中,我们将描述几个常用的函数,并给出它们的简单实现3 h+ Q' Q& a( H- P# b3 x7 _
# _' w2 `' A) _+ o' F首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?
* _& v, r8 z' C6 l' z0 A) v
( |$ ?! G) d) w( J4 e% }5 D: ?2 c9 O可能有些读者已经想到了,取第二个元素可以采用如下形式:
/ C7 D$ b ]! Q2 L
- ?# X9 v2 @1 A f3 l( F' k(car (cdr x))
) P. s& p; T- c0 O( } c0 m e. H' [
1 X: Z- X8 N& I3 {% X+ x4 d同理,取第三个元素是这样的:( \* W. W. P2 }2 [' y2 u; J
l) o6 R! ?# h% i& S6 u p) I(car (cdr (cdr x)))
5 [0 ]& _: D. C+ M9 u6 {* L4 E+ B, T
事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:
6 j ]6 X9 X2 h( Y
8 c3 K* E- y4 w> (cadr '((a b) (c d) e))7 C, X, d; d% |
(c d)7 E7 {4 k% F1 O" C8 r' w- a N5 p
> (caddr '((a b) (c d) e))$ u, L: C1 b0 ^" |3 h1 l
e
& o% r6 N7 ]) [' l- N5 O3 b/ t5 U> (cdar '((a b) (c d) e))0 R J& C7 I: T/ t
(b)
* R' ^/ b% {. H* M7 r# }4 M- ~ J2 P; f& A; q% B( q
另外,使用(list e1 e2 ... en)来表示
) ^+ w5 l8 A4 E4 O(cons e1 (cons e2 (... (cons en '())...)))
. D1 h+ t( ^ ^9 z
* a& V0 ~5 C, U7 c+ C* C" `> (cons 'a (cons 'b (cons 'c '())))
# a& e* W/ I# x0 {: d* y' y" N; v(a b c)
7 J7 S+ z% \& D8 n* t1 k, X5 O4 ]> (list 'a 'b 'c)$ j" A* P! t( r) K# H$ ?
(a b c)
$ H3 X! K$ w5 L1 Z, K1 Q' _
; W; w4 r: H0 N5 I现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。
$ p5 v# U& ~6 r$ A: m- o; Q9 V- D/ M
(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字)* N5 o5 @) S0 F. M6 y% a ~+ q5 h d
( `3 ]* L# f* S- e* s N% o& m(null x),测试x是否为空表。例如:6 `8 w! o1 |8 I9 m
> (null 'a)5 `# P8 l, l) C" f
()1 q! G4 L, ?1 ~' S, t1 D% L$ O* k
> (null '())
3 j: D* i( ~& q+ v3 ] D6 mt
" A z9 z0 a+ {% _& ^" C7 V(and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。
0 a2 Z( ~7 x5 f V9 @" q+ a> (and 'a 'b)& G! Q8 n& \( O) |
t
" V @0 b/ Y+ O9 ]0 b5 k& e2 k, b> (and (atom 'a) (eq 'b 'c))
# g4 c- h6 b) v: G: s/ l g3 N() ( W: z8 y$ W: t+ j. s: O7 X7 U2 E
(not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:
" u6 A9 A. G/ U+ H* z/ Z4 S> (not 'a)& m% B. W% W( b# L$ K( b
()/ E8 x; \8 y4 k' ^
> (not (eq 'a 'b))
/ N' R) H2 G# ]6 K7 N- j+ c% \' b9 U0 at / z! [" D. H4 |
(append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:
- K) x# n# `# v) f5 r0 x> (append '(a b) '(c d)). u! T% O( ` }, F; m2 v, B
(a b c d)7 @$ a/ i# O# X- k: Z
> (append '() '(x y))! R5 |- W8 q+ M( a7 H. w7 \( J
(x y) % V* `! `! f9 h+ O2 `; _# k
(pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:- w% P- \" G; }" i1 S/ ~ `& X
> (pair '(a b c) '(x y z))
+ l: I7 w/ w5 a! g3 S9 ^((a x) (b y) (c z))
' n$ W* H* Z- Y(assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:7 t4 ^& M* C" C1 w( ^6 U6 e! a
> (assoc 'a '((a x) (b y)))
S% Q+ e! G: M/ K% O; Ix
" O7 G! h4 Z J; W2 a. q2 Q> (assoc 'a '((a (foo bar)) (b y) (c z)))
; j+ x I& T# u9 c0 W7 w(foo bar) % u) ^) [8 Q3 g& Q/ K! {- ]+ b
(subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:$ `/ G" d5 h# t5 ^
> (subst '(x y) 'b '(a b (a b c) d))3 S6 a$ U3 r3 f- J% }8 }& e- `
(a (x y) (a (x y) c) d)
- E! O: K) {& Q* m3 _下面我们给出这些常用函数的简单实现:0 n0 l. V, h. C4 A
* W3 v" I, I5 V9 t(defun null (x)
5 q# d6 T: U% B) K (eq x '())) % z( A, B- g8 K, s) Y% n" A
(defun and (x y)
) X) e: E8 |# X (cond (x (cond (y 't) ('t '())))
% J# h# b% N& z ('t '())))
; o& {( T' v, K. K5 }0 x6 N(defun not (x)6 K6 H% X2 l- I- @8 e. v
(cond (x '())
8 W! F A$ j B3 {3 A3 c' R/ g ('t 't)))
- [1 X# {% ?1 x5 y(defun append (x y)6 s" Y$ `" n* u- V" l8 P i; z
(cond ((null x) y)4 m) ]% r3 f; d! i9 V! L8 t, s. }9 l
('t (cons (car x) (append (cdr x) y))))) 7 f- [! V1 w: ^& X
(defun pair (x y)
. b# d+ q, D& } (cond ((and (null x) (null y)) '())) {4 ]9 V ^; F1 n' m
((and (not (atom x)) (not (atom y)))
: |7 G% M6 J0 J+ m (cons (list (car x) (car y))
( z6 z5 C9 u; E5 i5 H (pair (cdr) (cdr y)))))) 1 U( A/ m2 }# `, A5 K: e; S
(defun assoc (x y)
- u- C9 x9 u' J (cond ((eq (caar y) x) (cadar y))+ _2 t. X* y- b- s+ _
('t (assoc x (cdr y)))))
! V7 b, }# l1 `3 h(defun subst (x y z)
% Y2 O, G8 r5 ~9 X" h; Y (cond ((atom z)
0 H/ t8 P+ Y" e (cond ((eq z y) x)
! B- S: I+ A/ Q% E ('t z)))7 C" X$ C' K9 \" j: A) D( Q+ |
('t (cons (subst x y (car z))5 ~8 h6 K# y/ @3 P
(subst x y (cdr z))))))0 ~9 e- } a8 V' Y
如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。4 Q. f& N" _8 C& T/ \% S
2 Z/ m# L- r' D这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。
/ P& F- K g$ Y
3 o4 z* A8 ?# f6 Q: A5 z# u理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。 |