Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:
# |* q, E5 v8 r$ D# Q/ u% ~) E9 a
既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述? : j" @0 N ?1 M# o3 ~* @$ y
在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数?
* h' B. \, J u% `3 [(注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)" I( k% Y& G6 S0 t
4 n6 m: \; }7 ?! K
这一集中,我们将描述几个常用的函数,并给出它们的简单实现
* C1 u- ]5 f2 r$ P* a Z6 l+ v {- d& Z: i/ y: z, p! R& J
首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?
+ l% e; c4 Z/ @( @0 k# Y8 N2 `& ~& k% q* o% F- o, B) x* F
可能有些读者已经想到了,取第二个元素可以采用如下形式:
, s+ R- Z0 B2 z: R6 D
* L7 r) h( n6 m(car (cdr x))4 I8 K Q9 B# G( j" l
, H2 u1 I; N( n+ V
同理,取第三个元素是这样的:# O- a# O% C, H% w+ E: w2 X& [# s5 G
8 B: s6 T1 V- O: e: l) q4 R(car (cdr (cdr x))) ]3 i( H$ H9 ?% Q) D8 l9 C% d
1 K7 J# @* B& j) H, ]* k事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:
9 o; z& l# O3 n+ b2 _( c0 [+ T! z9 S$ a7 N( F6 T# f
> (cadr '((a b) (c d) e))' m1 p& s) ]5 J" }
(c d). \* x* ?) G( P9 y
> (caddr '((a b) (c d) e)). z! y6 L5 U' ?# X. s
e
! C5 _- `- E" r. p& X. e> (cdar '((a b) (c d) e))/ I1 U8 A5 Y. h) W
(b)+ \, k" U- Z# R8 K
4 l( r+ ^& D( V/ I3 K, }
另外,使用(list e1 e2 ... en)来表示1 d9 X+ P! u5 M9 c+ e: m
(cons e1 (cons e2 (... (cons en '())...)))& }0 Y$ V$ e8 x0 @: A
$ {8 k, c& V1 V' D
> (cons 'a (cons 'b (cons 'c '())))6 k7 X2 `' E. O1 X$ L' {! A
(a b c)
c7 ?; H' s9 w1 T1 I4 L3 U3 w1 W> (list 'a 'b 'c)
/ O* F) T5 ]& |& B" M) f; z(a b c)
! e {. r: G( l, Z' i$ ~+ @ M3 }- Z5 {( a( ^
现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。
8 `) F" K- z; u) ^2 D
# v1 B6 {( b P& H(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字)/ h7 t7 p1 k/ r% D: w
* n8 H4 g! K/ ~: P' T
(null x),测试x是否为空表。例如:* g' S7 a r0 S! o m
> (null 'a)8 X1 i) v) T. j, J" {8 l. ^7 v
()
* W, c+ @+ C- S9 a( S% J T o8 U> (null '())
2 M$ D( o" ?1 E+ F$ R# nt 1 g* d' A; z7 Y8 y% r( g7 G
(and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。
8 Q+ r: @& Y. @; Q# F> (and 'a 'b)( \ Q; @3 i2 N3 V
t
' T, O3 T* }4 N3 i) b: S! G5 N5 u6 T> (and (atom 'a) (eq 'b 'c))( _- _% O7 o3 Q8 k, L4 S( D8 M
()
' }# d: b9 B! ?5 J' @(not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:- f5 T; e! J8 Q2 R
> (not 'a); d% o# K6 [" S6 I1 C% K
()
3 |4 N- w T4 S, F> (not (eq 'a 'b))
# V' ~' G: f$ R7 o' @t
X. q7 B2 ?+ u; `! }# t. i% | ~(append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:
/ J. ^6 L* {" b0 V9 U> (append '(a b) '(c d))
- D* Z- o4 N, H. p' t! K(a b c d), p! U2 }3 r& T" h
> (append '() '(x y))2 ]' b) D) H1 j5 k% n) x
(x y) # d1 v( b, w- v; ^! `
(pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:
, N( J& W$ h- c* k: o- L/ J7 S5 U> (pair '(a b c) '(x y z))
/ Y& L/ A) V) V* Q) y5 i9 O/ i((a x) (b y) (c z))
) Z: ^3 z% ]! O2 U4 ](assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:- |+ t. c6 S0 y4 T' r8 n1 M
> (assoc 'a '((a x) (b y)))' P3 g3 z2 H& ~) n! E5 {
x
. g! _" m2 M" f1 J* E* X" @3 t+ m3 ]> (assoc 'a '((a (foo bar)) (b y) (c z)))5 ?7 r. b9 |3 a& u3 N, V
(foo bar)
" P% c/ f9 X5 M- G/ ?2 @) E7 W(subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:
% J; T( R/ D* n( o8 T' F& t> (subst '(x y) 'b '(a b (a b c) d))/ T7 ]1 n+ Z' D& q1 L9 Q7 K5 M7 w+ M
(a (x y) (a (x y) c) d)6 v* A0 L" Q6 S4 g; P1 O
下面我们给出这些常用函数的简单实现:( \, }$ ^/ i! g" B( x' E
" |3 b' Q1 P8 j' J& Y; y(defun null (x)! V6 f, O) V! G. S1 t
(eq x '())) 9 O! u5 @) e ~2 ^3 L9 m v( U
(defun and (x y)# ~6 O, Z1 _* V/ v' r
(cond (x (cond (y 't) ('t '())))
- n( h t( \; T2 \ ('t '()))) " l5 `8 w; t+ c, B0 E
(defun not (x)# n% B- Y/ ?+ [$ i. H4 W
(cond (x '())* U4 J' Q6 ~9 n4 G, ?* B
('t 't)))
9 u5 y3 n9 g" j3 A& Y. x; b# f( B+ w(defun append (x y)
' H1 Z9 }% z. c: L+ \8 f (cond ((null x) y)! |, [% F- s* i
('t (cons (car x) (append (cdr x) y))))) & F# P8 l. q7 n* H
(defun pair (x y)4 [ p$ K+ P/ ~( I1 ~
(cond ((and (null x) (null y)) '())3 W( Y# A7 W f1 I7 T0 M
((and (not (atom x)) (not (atom y)))
% j X: ?# {5 d# v+ a7 \, @& z* f9 K: e (cons (list (car x) (car y))* [* F5 p3 U6 V
(pair (cdr) (cdr y)))))) 9 X) W1 T9 V; g7 W; \5 `( H
(defun assoc (x y)
. L- ], x0 r' [8 t: l7 e, a/ G (cond ((eq (caar y) x) (cadar y))
6 R& C: M' z+ }, k ('t (assoc x (cdr y)))))
* W0 @, l0 Q2 b5 u(defun subst (x y z)
! e3 R; K1 _8 i- j% X2 o (cond ((atom z). z$ Q# y1 w# o/ R' j5 `
(cond ((eq z y) x)
* \: e! M/ J) i1 o- F( N- }; m7 u ('t z)))
9 K% T( C, b7 ?/ K( R3 [1 F% h2 b ('t (cons (subst x y (car z))
2 y6 |3 r1 _2 Z3 n+ X3 y6 C (subst x y (cdr z))))))+ N7 H/ x0 e7 _
如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。6 Z( j+ D1 ^4 U: X8 H
! k* c: G0 y, k6 v, o; d" o
这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。
) E2 N6 E. O# T$ q; c
( N) N/ g7 _ c& E0 @理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。 |