Scheme:返回另一个内部过程的过程

Hou*_*ini 18 lisp scheme functional-programming sicp

这是来自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))
Run Code Online (Sandbox Code Playgroud)

所以在这里我理解car并且cdr正在定义范围内cons,并且我得到它们分别将一些参数映射z到1和0(参数z是一些cons).但是我说我打电话(cons 3 4)......当我们立即进入这个内部过程dispatch并且m我们还没有指定一些参数时,如何评估参数3和4 ?而且,更重要的是,返回的重点是dispatch什么?我根本没有真正得到那个部分.任何帮助表示赞赏,谢谢!

mic*_*kig 22

这是在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
Run Code Online (Sandbox Code Playgroud)

的版本cons,carcdr你已经粘贴行为应该完全相同的方式.我会试着告诉你如何.

首先,carcdr没有在范围内定义cons.在你的代码片段,所有这三个(cons,car,和cdr)在顶层定义.该函数dispatch是唯一在内部定义的函数cons.

该函数cons接受两个参数并返回一个参数的函数.对此重要的是这两个参数对于内部函数是可见的dispatch,这是返回的内容.我马上就会谈到这一点.

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

因此,使用这个基于函数的新对,我们需要能够调用car并传递该对作为参数,并获得第一个组件.在定义中car,这个参数被称为z.如果您使用这些new cons,carcdr函数执行上面的REPL会话,则参数zin car将绑定到基于函数的对,即cons返回,即dispatch.这很令人困惑,但只要认真思考就可以了.

基于其实现car,似乎它采用一个参数的函数,并将其应用于数字0.所以它适用dispatch0,正如您从定义中看到的那样dispatch,这就是我们想要的.在cond里面有比较m01,再返回xy.在这种情况下,它返回x,这是第一个参数cons,换句话说,该对的第一个组件!因此car选择第一个组件,就像普通原语在Scheme中一样.

如果按照这个相同的逻辑为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
Run Code Online (Sandbox Code Playgroud)

  • 哦......很好,我想我明白了.就像一个灯泡在我的头脑中继续!哇....所以在`(car(cons xy))`中,`car`计算它的参数(`cons`),它本身返回一个名为`dispatch`的过程._Then_`car`应用于该程序.并且因为`car`被定义为将其参数应用于0,_和_,因为`dispatch`是在'cons`的范围内定义的,它能够解析为`cons`的`x`值.听起来对吗?我当然希望如此......哈哈.尽管如此,仍然会回顾那一章! (3认同)
  • 不,`m`不是程序,它将是'0`或'1`.当我们评估`(car(cons 1 2))`并放入`cons`的主体时,内部函数`dispatch`只被定义然后返回,而不是被调用.所以在这一点上我们并不关心`m`.然后当`cons`返回时,我们需要评估像`(car#<function dispatch>)`这样的东西.在`car`的主体中,函数`dispatch`绑定到`z`,并使用参数`0`调用.因此`dispatch`从不知道`car`或`cdr`,它只将它的参数`m`与'0`和`1`进行比较,所以它期待一个数字. (2认同)
  • 是的,听起来你已经弄明白了!SICP是一本非常精彩的书,我很高兴能帮助一位程序员享受他们的思绪. (2认同)

Ósc*_*pez 6

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

dispatch过程充当传递给cons:x和的参数的选择器y.如果0收到消息,则cons返回第一个参数(car单元格的).同样,如果1收到,则cons 返回第二个参数(cdr单元格的).两个参数都存储在为dispatch过程隐式定义的闭包内,一个捕获的闭包,xy作为调用此过程实现的产品返回cons.

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

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


小智 5

这基本上就是"闭包/对象同构".

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

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

(define (cons x y)
  (lambda (method)
    (cond ((eq? method 'car) x)
          ((eq? method 'cdr) y)
          (else (error "unknown method")))))
Run Code Online (Sandbox Code Playgroud)

在哪种情况下,你有更接近OO的东西:

# (define p (cons 1 2))
# (p 'car)
1
# (p 'cdr)
2
Run Code Online (Sandbox Code Playgroud)