地方数千里,兵精足用,英雄乐业,当横行天下
——《资治通鉴》
Lisp的语法元素在前几集中已经基本讨论完毕,相比C#或Java数百页的Specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?现在让我们来回顾一下上一集中提到的内容,首先提几个问题:
既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述? 在(我们已经学过的)Lisp中如何表达“重复”这个语义?或者你能写一个foreach循环函数? (注:不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)
这一集中,我们将描述几个常用的函数,并给出它们的简单实现
首先解答在第一集中提出的问题:如何取一个表中的第二个、第三个或第n个元素?
可能有些读者已经想到了,取第二个元素可以采用如下形式:
(car (cdr x))
同理,取第三个元素是这样的:
(car (cdr (cdr x)))
事实上,这种组合在Lisp中经常要用到,为了方便,Lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:
> (cadr '((a b) (c d) e))
(c d)
> (caddr '((a b) (c d) e))
e
> (cdar '((a b) (c d) e))
(b)
另外,使用(list e1 e2 ... en)来表示
(cons e1 (cons e2 (... (cons en '())...)))
> (cons 'a (cons 'b (cons 'c '())))
(a b c)
> (list 'a 'b 'c)
(a b c)
现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。
(注:某些函数在Common Lisp中已经存在,所以如果你想试验一下,给它们换个名字)
(null x),测试x是否为空表。例如:
> (null 'a)
()
> (null '())
t (and x y),逻辑与,当且仅当x和y都不是空表时返回't,否则返回空表。
> (and 'a 'b)
t
> (and (atom 'a) (eq 'b 'c))
() (not x),逻辑非,当x是空表时返回't,否则返回空表。(有人问我or在哪儿?)例如:
> (not 'a)
()
> (not (eq 'a 'b))
t (append x y),连接两个表x和y,注意它与cons和list之间的不同之处。例如:
> (append '(a b) '(c d))
(a b c d)
> (append? '() '(x y))
(x y) (pair x y),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。例如:
> (pair '(a b c) '(x y z))
((a x) (b y) (c z)) (assoc x y),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。例如:
> (assoc 'a '((a x) (b y)))
x
> (assoc 'a '((a (foo bar)) (b y) (c z)))
(foo bar) (subst x y z),在表z中将任意层次上出现的原子y都替换为表达式x。例如:
> (subst '(x y) 'b '(a b (a b c) d))
(a (x y) (a (x y) c) d) 下面我们给出这些常用函数的简单实现:
(defun null (x)
(eq x '())) (defun and (x y)
(cond (x (cond (y 't) ('t '())))
('t '()))) (defun not (x)
(cond (x '())
('t 't))) (defun append (x y)
(cond ((null x) y)
('t (cons (car x) (append (cdr x) y))))) (defun pair (x y)
(cond ((and (null x) (null y)) '())
((and (not (atom x)) (not (atom y)))
(cons (list (car x) (car y))
(pair (cdr) (cdr y)))))) (defun assoc (x y)
(cond ((eq (caar y) x) (cadar y))
('t (assoc x (cdr y))))) (defun subst (x y z)
(cond ((atom z)
(cond ((eq z y) x)
('t z)))
('t (cons (subst x y (car z))
(subst x y (cdr z)))))) 如果看到这里你还没有晕菜,说明你的神经的确很坚强。注意在这些例子中是如何表达“重复”这个概念的,在Lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。
这一部分内容可以让你真正感受到Lisp的特色,与编写过程式语言的程序相比,编写Lisp程序需要一种完全不同的思维方式,也许这正是Lisp语言几十年来长盛不衰的真正原因吧。
理解了这一部分,下一集中我们将领教一下Lisp的威力,我们将用Lisp编写一个Lisp解释器。如果你以前没有见过这个程序,我保证它一定会让你吃惊。
广告之后我们再回来…………