理解S表达式

S表达式,是Lisp语言的特色,

让我们有能力对程序和数据进行统一编码。


这种一致性,

使得我们可以和处理数据一样处理Lisp代码,

正因为这样,才可以建立其他语言无法比拟的宏系统(macro system)。


S表达式真是天才之作呀。

它是凭空想出来的吗?


不是的。

S表达式是二叉树的一种线性编码


二叉树

我们知道二叉树是很重要的数据结构,

可以用来存储结构化的数据。

例如:

1
2
3
4
5
        *
/ \
* *
/ \ / \
a b c d


二叉树的每个节点,

或者是叶节点,或者有2个子节点,

叶节点可以用来存储数据。


可是,这样表示二叉树,太麻烦了。

不容易书写。


于是,先哲们发明了点对表示法

((a . b) . (c . d))可以表示上面的二叉树,

其中“.”表示节点。


S表达式是点对表示法的形式定义:

1
2
原子 -> 数字 | 符号
S表达式 -> 原子 | (S表达式 . S表达式)


所以,S表达式或者是原子,

或者是递归的由其他S表达式构成的点对。


S表达式的化简

实际使用时,

书写S表达式,还要同时写很多点号“.”,

这也是一件麻烦的事情。


因此,Lisp语言定义了一套S表达式的化简规则

(1)如果一个点号右邻一个左括号,那么就可以将这个点号,左括号以及匹配的右括号,一起去掉。

例如:(a . (b . c)) <=> (a b . c)


(2)如果一个点号右邻原子nil,那么就可以把这个点号和原子nil,一起去掉。

例如:(a . (b . nil)) <=> (a b . nil) <=> (a b)


列表是一种特殊类型的S表达式,

如果一个S表达式不是原子,

而且经过化简可以把点号都去掉,

就说这个S表达式是一个列表。


例如:(a . (b . (c . nil))) <=> (a b c)

是一个列表。


为了使用方便,还允许有空表

而且还定义空表等价于原子nil

() <=> nil


例如:(a . (b . ())) <=> (a . (b . nil)) <=> (a b)


值得注意的是,

空表的记法,使nil身兼三职,

(1)nil表示一个原子

(2)nil表示空表

(3)nil表示逻辑值“假”

nil成为了Lisp语言中唯一的既是原子又是列表的表达式。


常用函数pair?,cons和car

Scheme语言是Lisp的一种方言,

它没有nil的概念,只有空表()


在介绍常用函数之前,

我们需要知道,Scheme语言的求值策略(evaluation strategy)。


Scheme语言中的函数是按值调用的(call by value),

在求值函数体之前,实参会先被求值。


因此,(display (* 2 3))中,

(* 2 3)并不会被看成是具有3个元素的列表,而是看成乘法函数调用,

display调用之前,会先求值(* 2 3) => 6,结果是显示6


假如,我们一定要把(* 2 3)看成是列表呢?

就需要引用它。(display '(* 2 3))

此时,'(* 2 3)求值为列表(* 2 3)

结果是显示(* 2 3)


我们来看pair?函数,

它只有在参数是点对的情况下返回#t,其余都返回#f

例如:

1
2
3
4
(pair? '(a . c)) => #t
(pair? '(a b c)) => #t
(pair? '()) => #f
(pair? 'abc) => #f


值得注意的是,

(1)空表不是点对。

因为它是原子nil,虽然Scheme语言中没有内置原子nil

可以通过另外一个函数看出来,atom?用来判断参数是否原子。

(atom? '()) => #t


(2)列表是点对的简写。

(a b c) <=> (a . (b . (c . ())))


我们再来看cons函数,

cons接受两个参数,将他们拼成一个点对,

结果是化简后的S表达式。

例如:

1
2
3
(cons 'a 'b) => (a . b)
(cons 'a '(b)) => (a . (b)) <=> (a b)
(cons '(a) '(b)) => ((a) . (b)) <=> ((a) b)


最后来看一下carcdr函数,

它们接受一个点对作为参数,

car获取点对的左元素,cdr获取右元素。

1
2
3
4
(car '(a . b)) => a
(cdr '(a . b)) => b
(car '(a b)) <=> (car '(a . (b))) => a
(cdr '(a b)) <=> (car '(a . (b))) => (b)


结语

刚学Lisp语言的时候,

查看了很多书,对S表达式的解释,总是不够清楚。


无意间找到了马希文老师的《LISP语言》,

书中用中国人的方式写了对Lisp语言的理解,

言简意赅。


让我认识到了,

真正精髓的知识,都一定是简单的,

难以理解的东西,可能作者也没有搞明白。