Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:
! \% ~# C& Q1 n' H1 O2 ?& a; U- n1 m/ }- _1 P
既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述?
) x6 V( S# w' x% p! h- Q在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数?
: }7 c/ w1 V7 o; q. `2 x(注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)
[$ F" o3 O$ j3 m2 H& s$ `- Y! u6 k% L6 e5 g
这一集中,我们将描述几个常用的函数,并给出它们的简单实现
3 U2 A+ F" _4 G' @! p3 W* z( ^4 R1 ~2 v
首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?6 y: v: @4 ~9 @7 X
* @9 z) ^2 {' n# e1 F. ?可能有些读者已经想到了,取第二个元素可以采用如下形式:
8 t& W0 q* I( L' f
) K. K9 Y: G* A9 F" l, z(car (cdr x))
- ]* k3 J; l8 x/ }/ @" D
8 R1 S$ W- E1 b \$ ^同理,取第三个元素是这样的:
0 l) N1 f$ v. l+ d; I' F2 M. t8 X, h
(car (cdr (cdr x)))3 g4 E9 z5 x+ ^9 k
8 a% `& p$ u8 k( ~* q事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:
* P- m& {; }; r% q$ L4 ?' P
6 ~7 ?1 `! g& @) z% X> (cadr '((a b) (c d) e))
' B9 B1 f+ n8 ]: T6 T8 T(c d)
}, a; K" Q+ ^+ D/ r> (caddr '((a b) (c d) e))' ~. k" o6 z& `% k% L
e
, d% Q# ]% O7 y; c/ [> (cdar '((a b) (c d) e))' o5 E8 A0 V6 o/ e
(b)7 b/ Z) e" v( s, u
& p8 j* {' r# X1 t
另外,使用(list e1 e2 ... en)来表示& ^( g8 V3 M4 U) v
(cons e1 (cons e2 (... (cons en '())...)))
0 Q5 }' D$ ?8 \4 ^- ^1 j
$ Q* R6 d- u: g: m+ U! f9 K> (cons 'a (cons 'b (cons 'c '())))
+ f8 t, o4 g& U' @(a b c)
8 [, J/ D0 p, ]+ |2 N" H2 {" {> (list 'a 'b 'c)
! E$ `* q; ~3 b* j* o8 D(a b c)- @6 P) Q: m: h: m; x' {7 D
) k0 k% Z. y M/ L! {8 L
现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。
) @ p- n# Q/ d1 K, B
; H; q! Z( X3 j3 C; l- L(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字)# R# C, X8 b- v4 Y ]" F8 B
5 k6 V% l* c5 O
(null x),测试x是否为空表。例如:+ u, J/ E8 O1 e$ s1 S6 ~& [
> (null 'a)/ i# r& `7 J% R7 z
(); Q; Q/ X' t. ?2 y Q5 B7 W8 P
> (null '())' C/ b+ `1 P; O' H
t
2 x, ?/ g( _" T1 e! f% s1 S$ a$ n(and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。
0 S( W1 ]9 u+ l6 l( p/ B, O> (and 'a 'b)
& l, ~4 P/ x# f# ~' Y! e; wt$ a6 ~6 [0 M/ }& I
> (and (atom 'a) (eq 'b 'c))
. i7 g# H* J/ S9 `- Q1 d' S() - y, q# {& |5 r$ o% q1 L. M) s
(not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:2 F `0 _) `) I+ \' [0 t
> (not 'a)
$ T1 J- ?, s4 M6 }5 s+ I()
& @/ g6 M+ Z: s4 J* {- e; S> (not (eq 'a 'b)), s3 Q* c6 A9 o
t * n& [0 Q. s& i1 T0 J9 b! d" e- l
(append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:/ R5 ?3 P2 [; m! _% z; I/ G
> (append '(a b) '(c d))
, ~6 Z: c! Q* `" Z+ ]9 d/ m$ X+ Y(a b c d)
6 \' T% n. _. C9 k* f> (append '() '(x y))9 `9 }5 g$ U1 c; J8 F
(x y)
# E! k4 T& ]" m0 F; C7 c& | g# ]7 z(pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:
* p" P& r4 F' @" C3 H4 U T9 J> (pair '(a b c) '(x y z))7 i; V8 N+ g: ]# u7 P( Z8 c( J
((a x) (b y) (c z))
" u! W p* d! d0 U, N(assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:' N# C- x7 J- D; T
> (assoc 'a '((a x) (b y)))1 y1 V! L4 v( l; t
x. W/ |/ ]" C# e5 i- [( W' w0 y- z
> (assoc 'a '((a (foo bar)) (b y) (c z)))& U! A: Y8 I$ c) I$ S) Y
(foo bar)
& {" Q! u5 N" ^6 Q' e# Q+ D(subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:% i* \5 N, R# @9 w" \
> (subst '(x y) 'b '(a b (a b c) d))
7 a0 h& z }6 x a7 [9 Q# l8 K(a (x y) (a (x y) c) d)2 W, s. D- x2 u* M1 T- f
下面我们给出这些常用函数的简单实现:% b3 U( }, e! L' q r" R1 d
% {; H0 s$ a' i3 A* {
(defun null (x)0 j$ g! l5 A" @& c2 L9 I
(eq x '())) ' b# f6 C; W# |( _. P4 Q% I
(defun and (x y)- r9 F/ j2 C" W2 {7 w" R
(cond (x (cond (y 't) ('t '())))
( [1 _( ^% ?) S. r0 _ ('t '()))) ; Y3 n9 S4 z/ o
(defun not (x)9 q: X1 {+ l- `( f$ Z' {
(cond (x '())
+ x8 u# f% ]$ l3 p" \, B* x' J ('t 't))) * L/ [) d7 U" k
(defun append (x y)
1 m E# h, i& a: o* r (cond ((null x) y)
8 N( R0 x3 K$ P( x8 T" @0 a ('t (cons (car x) (append (cdr x) y))))) : R! Z# J1 V* z
(defun pair (x y)) E5 ]9 u. Y% h$ L( Y8 s
(cond ((and (null x) (null y)) '())
& p7 O- B; L& X+ E& ^ ((and (not (atom x)) (not (atom y)))! c( X. |& n, r% f" N+ h+ [
(cons (list (car x) (car y))
3 O) m, {8 q1 v+ K' B) ]: X (pair (cdr) (cdr y)))))) 1 Y4 I. l- w5 g# F! f: V; X8 P
(defun assoc (x y)
2 Y9 z/ A- ^& L5 e* h) X (cond ((eq (caar y) x) (cadar y))2 r5 I# h) ]0 ]* C8 }$ R- @
('t (assoc x (cdr y)))))
; j3 c* D0 P/ |1 {(defun subst (x y z)
- {6 X" t& [# \" H5 M& m& ?; N" E (cond ((atom z)
& Z3 N) a6 _4 D1 e (cond ((eq z y) x)
, ?. K1 \) O$ P ('t z)))
+ y- |! `. | L* `) t6 { ('t (cons (subst x y (car z))& h) _, u J: c4 N* W
(subst x y (cdr z))))))
1 Q* M) f6 `9 T8 u' y如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。
. \' C/ @+ {4 h: G6 g- \& l
) c K4 L4 R5 b- x4 `% S- ]这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。
; e6 f! L7 r1 x( L- J; o( W( p- }. s- q( z) u
理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。 |