Skip to content
On this page

第四课:特殊形式

上一课提到了 (define ...)。这种东西长得很像组合式/过程调用,对吧?但它并不是组合式。为什么呢?

组合式有着固定的计算方法:

  1. 首先,对运算符(过程)进行求值。比如,如果运算符是一个“变量名”,那么找到它所对应的定义。
  2. 其次,按顺序对每一个运算对象(实参)求值。比如,计算 (/ (+ 2 3)) 的第二步就是计算 (+ 2 3) 的值。需要这样一直做下去直到运算对象是平凡的(比如就是一个整数)。
  3. 最后,将运算对象绑定到形参名字上,计算结果。

然而,(define ...) 不能这样求值。用 (define size 2) 举例:如果按照组合式的计算方法来做的话,那么就要尝试去对 size2 求值——不妙,size 并不是已经定义的变量名(而是即将要定义的变量)。导致的结果就是,解释器不知道 size 是什么东西,这样子行不通。

因此,像 (define ...) 这种东西,称为 特殊形式。特殊形式类似于其它语言的 关键字,具有特殊的语法和特殊的计算方法。比如 define 的计算方法就是,将第一个“东西”作为变量名添加到当前的解释环境,其值为第二个“东西”求值后的结果(仅就变量定义而非过程定义)

下面介绍一个常用的特殊形式:if。先看一个例子:

scheme
(define (abs x)
        (if (>= x 0)
            x
            (- x)))

从名字可以看出,代码定义了一个计算绝对值的 abs 过程。绝对值的定义是:如果 x 是非负数,那么 x 的绝对值就是 x;否则 x 的绝对值是 -x。代码里用 if 特殊形式来实现它。if 接受三个“东西”:

scheme
(if 条件 真分支 假分支)

如果 条件 成立,那么就返回 真分支 的结果;否则返回 假分支 的结果。这里的 条件 就是 (>= x 0):其中 >= 过程判断 x 大于等于 0 是否成立。如果成立,那么整个 if 的计算结果就是第三行的 x;否则就是第四行的 (- x)

TIP

>= 过程,当然也有 < = > <= 等等。它们都是比较整数的大小;如果成立就返回 #t(就是真命题、true 的意思),不成立时返回 #f(就是假命题、false 的意思)。

在 R5RS 中,只有 #f 代表条件不成立,其余的所有值,包括 #t,都代表条件成立。

为什么说 if 是特殊形式而非过程呢?因为 if惰性求值的。它一上来只会对 条件 求值,如果为真才会求值 真分支 并扔掉 假分支 上的表达式不管。反之,就扔掉 真分支 只求值 假分支。如果按照过程的求值方式,那么条件和两个分支都会被求值后才会去进行取舍:这样做的效率会比较低,尤其是表达式很长的时候。

TIP

if 的惰性求值是在 Lisp 中实现递归算法的必要条件。想想看,为什么?

如果将 if 特殊形式对应于 C 里的 if 语句,那么 switch 语句在 Scheme 里对应于一个叫 cond 特殊形式,其原理是类似的我们这里不多提及。但是 C 里的循环语句 while 或者 for,在 Lisp 里是没有这样的概念的。那怎么写循环的逻辑呢?别急,马上就讲