19

这是我相信你们中的许多人都熟悉的 SICP 书中的内容。这是本书中的一个早期示例,但我觉得一个非常重要的概念,我还无法理解。这里是:

(define (cons x y)
 (define (dispatch m)
   (cond ((= m 0) x)
         ((= m 1) y)
         (else (error "Argument not 0 or 1 - CONS" m))))
 dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))

所以在这里我理解car并被cdr定义在 的范围内cons,并且我知道它们z分别将一些参数映射到 1 和 0 (参数z是 some cons)。但是假设我调用(cons 3 4)...如何评估参数 3 和 4,当我们立即进入这个内部过程时dispatch,它需要一些m我们尚未指定的参数?而且,也许更重要的是,返回 ' 有什么意义dispatch?我根本不明白那部分。任何帮助表示赞赏,谢谢!

4

3 回答 3

23

这是在 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我会试着告诉你怎么做。

首先,carcdr没有定义在cons. 在您的代码片段中,所有三个(conscarcdr)都在顶层定义。该函数dispatch是唯一在内部定义的函数cons

该函数cons接受两个参数并返回一个参数的函数。重要的是这两个参数对内部函数是可见的dispatch,这就是返回的内容。一会儿我会讲到的。

正如我在提醒中所说,cons构造一对。这个版本cons应该做同样的事情,但是它返回一个函数!没关系,我们并不真正关心这对是如何在内存中实现或布局的,只要我们能得到第一个和第二个组件。

因此,对于这个基于函数的新对,我们需要能够调用car该对并将其作为参数传递,并获取第一个组件。在 的定义中car,这个参数被称为z。如果您要使用这些 new conscarcdr函数执行我上面的相同 REPL 会话,则参数zincar将绑定到基于函数的对,这就是cons返回的内容,即dispatch. 这很令人困惑,但只要仔细考虑一下,你就会明白。

基于 的实现car,它似乎采用一个参数的函数,并将其应用于数字0。所以它适用dispatch0,从 的定义中可以看出dispatch,这就是我们想要的。内部与andcond进行比较并返回or 。在这种情况下,它返回,这是 的第一个参数,也就是该对的第一个组件!所以选择第一个组件,就像在 Scheme 中正常的原语一样。m01xyxconscar

如果您对 遵循相同的逻辑cdr,您会发现它的行为方式几乎相同,但将第二个参数返回给cons, y,这是该对的第二个组成部分。

有几件事可以帮助您更好地理解这一点。一种是回到第 1 章中对求值替换模型的描述。如果你仔细而细致地遵循这个替换模型,用一些非常简单的例子来使用这些函数,你会发现它们是有效的。

另一种不那么乏味的方法是尝试dispatch直接在 REPL 中使用该功能。下面,变量p被定义为引用dispatch返回的函数cons

> (define p (cons 1 2))
#<function> ;; what the REPL prints here will be implementation specific
> (p 0)
1
> (p 1)
2
于 2012-09-19T14:28:57.310 回答
7

问题中的代码显示了如何重新定义cons创建cons-cell的原始过程(一对两个元素:汽车和 cdr),仅使用闭包和消息调度。

dispatch过程充当传递给cons:x和的参数的选择器y。如果收到消息,则返回0的第一个参数(单元格的)。同样,如果收到,则 返回的第二个参数(单元格的)。两个参数都存储在为该过程隐式定义的闭包中,该闭包捕获并作为调用.conscar1conscdrdispatchxycons

下一个重新定义carcdr以此为基础:car实现为传递0给上述定义中返回的闭包cdr的过程,并实现为传递1给闭包的过程,在每种情况下最终返回作为传递的原始值xy分别。

这个例子真正好的部分是它表明 cons-cell 是 Lisp 系统中最基本的数据单元,可以定义为过程,因此模糊了数据过程之间的区别。

于 2012-09-19T14:32:05.977 回答
6

基本上,这就是“闭包/对象同构”。

外部函数(cons)是一个类构造函数。它返回一个对象,该对象是一个参数的函数,其中参数等同于方法的名称。在这种情况下,方法是 getter,因此它们评估为值。您可以轻松地在构造函数返回的对象中存储更多过程。

在这种情况下,选择作为方法名称的数字和在对象本身之外定义的含糖过程。你可以使用符号:

(define (cons x y)
  (lambda (method)
    (cond ((eq? method 'car) x)
          ((eq? method 'cdr) y)
          (else (error "unknown method")))))

在这种情况下,您所拥有的更类似于 OO:

# (define p (cons 1 2))
# (p 'car)
1
# (p 'cdr)
2
于 2015-08-03T09:32:18.677 回答