这是在 Scheme 中利用一等函数的最奇怪的(也可能是更精彩的)示例之一。 Little Schemer 中也有类似的东西,这是我第一次看到它的地方,我记得为它挠了好几天头。让我看看我是否可以用有意义的方式解释它,但如果不清楚,我深表歉意。
我假设您理解原语 cons、car 和 cdr,因为它们已经在 Scheme 中实现,但只是提醒您:cons 构造一对,car 选择第一个组件该对并返回它,cdr 选择第二个组件并返回它。以下是使用这些函数的简单示例:
> (cons 1 2)
(1 . 2)
> (car (cons 1 2))
1
> (cdr (cons 1 2))
2
您粘贴的cons、car 和cdr 版本的行为方式应该完全相同。我会试着告诉你怎么做。
首先,car和cdr没有定义在cons的范围内。在您的代码 sn-p 中,所有三个(cons、car 和 cdr)都在顶层定义。函数dispatch 是唯一在cons 中定义的函数。
函数cons 接受两个参数并返回一个参数的函数。重要的是这两个参数对内部函数dispatch 是可见的,这就是返回的内容。稍后我会讲到。
正如我在提醒中所说,cons 构造了一对。这个版本的cons 应该做同样的事情,但是它返回一个函数!没关系,我们并不关心这对是如何在内存中实现或布局的,只要我们能够了解第一个和第二个组件即可。
因此,对于这个基于函数的新对,我们需要能够调用car 并将该对作为参数传递,并获取第一个组件。在car 的定义中,这个参数称为z。如果您要使用这些新的cons、car 和cdr 函数执行我上面的相同REPL 会话,car 中的参数z 将绑定到基于函数的对,即cons 返回的是 dispatch。这很令人困惑,但仔细考虑一下就会明白。
基于car的实现,它似乎是接受一个参数的函数,并将其应用于数字0。所以它将dispatch 应用到0,从dispatch 的定义可以看出,这就是我们想要的。里面的cond 将m 与0 和1 进行比较,并返回x 或y。在这种情况下,它返回x,这是cons 的第一个参数,也就是该对的第一个组件!所以car 选择第一个组件,就像Scheme 中的普通原语一样。
如果您对cdr 遵循相同的逻辑,您会发现它的行为方式几乎相同,但将第二个参数返回给cons、y,这是该对的第二个组成部分。
有几件事可以帮助您更好地理解这一点。一种是回到第 1 章中对求值替换模型的描述。如果您仔细、细致地遵循该替换模型,使用这些函数的一些非常简单的示例,您会发现它们是有效的。
另一种不那么繁琐的方法是尝试直接在 REPL 中使用 dispatch 函数。下面,变量p被定义为引用cons返回的dispatch函数。
> (define p (cons 1 2))
#<function> ;; what the REPL prints here will be implementation specific
> (p 0)
1
> (p 1)
2