Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:
: ?8 U* z6 Z( {9 v9 n& [
/ j' L8 z! ~1 ^/ @( G+ S, w既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述? 8 D, ?, |- l5 f( [
在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数?9 G5 O }% c v* v# p7 k
(注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)+ _3 @5 z( K1 Z2 \5 C$ ?: V
+ w* f8 J( @1 D这一集中,我们将描述几个常用的函数,并给出它们的简单实现
8 `) N* n% C8 _2 g5 u* j+ O9 { Z5 N0 S
首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?2 H# b1 A5 y* [* W0 n
! o* N+ x5 f8 P3 j: l2 w8 x) o可能有些读者已经想到了,取第二个元素可以采用如下形式:
$ G9 T$ x# ^( N7 S t8 m& s3 E; `; ?* c1 S; Z
(car (cdr x))& |% l# Z, V0 Q- r8 w0 \- B$ N* h" |$ b
0 ` x9 M, t. V# U( R6 { U9 t
同理,取第三个元素是这样的:
& O9 P. T/ c7 M; B( s2 i
+ T- ~( v3 t E( ~- E(car (cdr (cdr x)))& b. v) ?. ]& `: p- }9 t
/ w9 O2 D. |$ P
事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:: {1 E6 p+ Q& G6 E% ?
# N$ [* l: F6 F, L7 y7 ]5 s> (cadr '((a b) (c d) e))
2 d) w2 Y' x5 Q3 R1 d0 ]- y* u T. c(c d)
5 {% `/ B; N6 g: r z/ ^1 Z> (caddr '((a b) (c d) e)); a. N. L" Q6 V) {
e5 H; F+ l d/ `( \
> (cdar '((a b) (c d) e))3 U/ H. H7 j2 n; t, x# Z6 c0 R
(b)+ {/ k1 }2 ~5 G% r/ t
3 F0 z* l! ~& p# J1 G! U+ W另外,使用(list e1 e2 ... en)来表示! s& U7 W8 k, |# f
(cons e1 (cons e2 (... (cons en '())...)))
( T/ I/ Z& H# x* @9 j" S0 z; p8 X0 i) [" [3 t6 X+ `- J7 L
> (cons 'a (cons 'b (cons 'c '())))
' ~# J9 q U& j5 K% Q(a b c)% C+ E; f! {6 |. S9 Q9 L) D
> (list 'a 'b 'c)
$ a) K3 M, n0 `, o(a b c)
0 M$ u: Q3 t' v' z: {4 o; }# W' `2 T5 D& I5 A
现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。' c: [- t& f. s* |
/ U5 D8 h0 {8 R$ {8 V+ r(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字). ^# L& q! p( u) b: R- d" \- V
; ]; e( Z2 g/ X) L
(null x),测试x是否为空表。例如:
# I% t U) L9 z {4 L4 C: e> (null 'a)
; i7 i* u5 i0 c q6 I8 s()$ H- w# O3 n% n$ J8 O% l2 ^: T
> (null '())
$ ?! L' N; u7 a2 h& Et 2 u' M' `2 k A. J# B( J8 i* r
(and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。: d' }. t4 B: Z' X
> (and 'a 'b)
6 M k8 H7 K! E) b2 T1 u+ yt
3 K& \. u7 v2 J7 s% |> (and (atom 'a) (eq 'b 'c))
0 b0 C5 l; l( k: q8 W, u+ V()
* e" C! {# v; J! Q3 k(not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:
8 g, g- t6 B, `# W: G3 B. A- [8 e> (not 'a). Z! [; i7 Q6 I; t0 ]6 w
()
& i. ], u3 H" z5 A' J> (not (eq 'a 'b))9 g: Z" b! e' o1 n, W9 K
t # Q) [9 Z$ c! P( F
(append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:
' G, I6 q# A# c$ \ c, U$ ^> (append '(a b) '(c d))8 W2 p/ ?1 Q! K C/ G
(a b c d)5 J c) O( p; t: ^* V0 _7 s
> (append '() '(x y))
- M; n+ v! ?$ T+ J9 D3 `2 ?(x y)
2 L( w! w5 Y" l6 R! A/ Y' N(pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:) e% ^5 N) j9 I
> (pair '(a b c) '(x y z))2 _1 {5 ?, p+ ]& M" v
((a x) (b y) (c z)) 7 F! {1 b# a* Q; k/ U6 L5 M
(assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:5 Q8 P3 }1 c# R& [
> (assoc 'a '((a x) (b y)))7 t. C; C2 H, D" G, i* z7 S$ Q [4 s
x
' E8 ]9 n0 s3 b/ H4 P* {8 K> (assoc 'a '((a (foo bar)) (b y) (c z)))
. B) V0 E, B7 l0 ?, x) L; M! [3 E(foo bar) ) d1 L: U7 l e& t" z3 Y
(subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:
+ L# a8 {* e6 I. l- ~7 N. Y: I2 {, ^> (subst '(x y) 'b '(a b (a b c) d))& j" O5 @9 Y0 `) {0 {, ]) k% x8 v
(a (x y) (a (x y) c) d)# U( j- I$ r/ D9 T/ ^6 Z
下面我们给出这些常用函数的简单实现:
! X) V: V/ T7 s6 A( v3 Q5 D" `; A* @4 H; U' w
(defun null (x)( |) V9 @8 w/ r
(eq x '())) : w4 f" ]3 m2 \, g9 ]
(defun and (x y)
0 e, x, S, b$ | (cond (x (cond (y 't) ('t '())))
$ I$ t; N" `: D ('t '())))
8 l* Q3 t' [ N0 C, L3 M! S* ^(defun not (x)
( W/ k; Y" P0 j4 D (cond (x '())& M1 {/ s) v- W9 P3 |. s/ _4 F
('t 't))) - J/ E7 g, U7 x7 J: F3 J0 j9 F$ p1 @& R! J
(defun append (x y)
! a0 n& R* F, Q$ x, `5 r (cond ((null x) y)$ }) i0 d8 g; ^2 u7 T
('t (cons (car x) (append (cdr x) y)))))
& [- a. A0 ^0 ]9 \5 F(defun pair (x y). b4 c7 L/ r7 N2 d9 t7 Q! n
(cond ((and (null x) (null y)) '())
3 D/ K/ z( \, T5 R( z ((and (not (atom x)) (not (atom y)))7 |, M/ `5 V7 b2 ?9 p! K! q) E
(cons (list (car x) (car y))
" k3 L, g9 E6 a (pair (cdr) (cdr y))))))
2 F* a% c* c* J5 t2 t& Z. R8 r(defun assoc (x y)
5 r8 r \/ Q$ g& B7 c* _& s6 V (cond ((eq (caar y) x) (cadar y))
' i1 O: Y2 a P9 R& S% J ('t (assoc x (cdr y))))) 5 j2 M* f( {7 ~" K7 ~" `) K6 R) ]
(defun subst (x y z)) q3 N9 r x0 D3 `& {" b' t0 h
(cond ((atom z)
1 j4 V* s# h C; s (cond ((eq z y) x)3 T( p/ _( B1 \: f; q# d/ W
('t z)))$ E1 E3 q2 `8 u' F
('t (cons (subst x y (car z))- I, H7 L( e: _4 s/ F3 M. z& m
(subst x y (cdr z)))))). p# B1 {* M5 ?* A$ n
如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。
6 D4 e( F- ~. i2 o8 ?4 u7 l) B1 J& `- }; P
这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。8 O0 ^- D/ U; r; d* X) W
* f. `. l0 S% ?理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。 |