Appearance
第四课:特殊形式
上一课提到了 (define ...)
。这种东西长得很像组合式/过程调用,对吧?但它并不是组合式。为什么呢?
组合式有着固定的计算方法:
- 首先,对运算符(过程)进行求值。比如,如果运算符是一个“变量名”,那么找到它所对应的定义。
- 其次,按顺序对每一个运算对象(实参)求值。比如,计算
(/ (+ 2 3))
的第二步就是计算(+ 2 3)
的值。需要这样一直做下去直到运算对象是平凡的(比如就是一个整数)。 - 最后,将运算对象绑定到形参名字上,计算结果。
然而,(define ...)
不能这样求值。用 (define size 2)
举例:如果按照组合式的计算方法来做的话,那么就要尝试去对 size
和 2
求值——不妙,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 里是没有这样的概念的。那怎么写循环的逻辑呢?别急,马上就讲。