Scheme:返回另一个内部过程的过程
Posted
技术标签:
【中文标题】Scheme:返回另一个内部过程的过程【英文标题】:Scheme: Procedures that return another inner procedure 【发布时间】:2012-09-11 20:47:46 【问题描述】:我相信你们中的许多人都熟悉这本 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
是一些@ 987654327@)。但是假设我调用(cons 3 4)
...当我们立即进入这个内部过程dispatch
时,如何评估参数3 和4,它需要一些我们尚未指定的参数m
?而且,也许更重要的是,返回 'dispatch
有什么意义?我根本不明白那部分。任何帮助表示赞赏,谢谢!
【问题讨论】:
【参考方案1】:这是在 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
【讨论】:
好的,很好的答案,我一定会回顾这本书的那部分。但是m
呢?我从来没有看到任何东西直接传递给调度。在我看来m
将是过程car
或cdr
。真的吗?例如,我们评估(car (cons 1 2))
。这将首先评估car
的参数,因此它将查看(cons 1 2)
。这将落入dispatch
....的定义中,它正在寻找m
。那么dispatch怎么知道m
是cons
的car
或者cdr
呢?如果这个问题没有意义,请告诉我,我会尝试改写它:)
不,m
不是一个过程,它将是 0
或 1
。当我们评估(car (cons 1 2))
并放入cons
的主体时,内部函数dispatch
只是被定义然后返回,还没有被调用。所以那时我们并不关心m
。然后当cons
返回时,我们将需要评估类似(car #<function dispatch>)
的内容。在car
的主体中,函数dispatch
绑定到z
,并使用参数0
调用。所以dispatch
永远不知道car
或cdr
,它只将它的参数m
与0
和1
进行比较,所以它期待一个数字。
哦……很好,我想我明白了。它就像一个灯泡在我的脑海里亮着!哇......所以在(car (cons x y))
中,car
评估它的参数 (cons
),它本身返回一个名为 dispatch
的过程。 然后 car
应用于该过程。并且由于car
被定义为将其参数应用于0,并且 因为dispatch
在cons
的范围内定义,所以它能够解析为@ 的x
值987654416@。听起来对吗?我当然希望如此……哈哈。不过,还是会重读那一章!
是的,听起来你已经明白了! SICP 是一本非常棒的书,我很高兴能帮助其他程序员享受让他们的思想受到启发的乐趣。
你帮了我很多,希望其他程序员也一样。再次感谢!【参考方案2】:
问题中的代码显示了如何重新定义原始过程 cons
,该过程创建 cons-cell(一对两个元素:汽车和 cdr),仅使用 closures 和消息调度。
dispatch
过程充当传递给cons
的参数的选择器:x
和y
。如果收到消息0
,则返回cons
的第一个参数(单元格的car
)。同样,如果收到1
,则返回cons
的第二个参数(单元格的cdr
)。这两个参数都存储在为dispatch
过程隐式定义的闭包中,该闭包捕获x
和y
,并作为调用cons
的此过程实现的产物返回。
car
和 cdr
的下一个重新定义基于此:car
被实现为将0
传递给上述定义中返回的闭包的过程,cdr
被实现为一个过程将1
传递给闭包,在每种情况下最终返回分别作为x
和y
传递的原始值。
这个例子真正好的部分是它显示了 cons-cell,Lisp 系统中最基本的 data 单元可以定义为 procedure ,因此模糊了 data 和 procedure 之间的区别。
【讨论】:
谢谢!另一个答案更具描述性,并为我提供了书中的参考,但您的回答肯定也有助于我理解这个概念。尤其是 data 实际上可以表示为 procedure 的想法。老实说,在我查看书中的这段代码之前,我什至不明白这意味着什么......虽然我们通常会将类似cons
的东西表示为数据结构或变量,但有了这个想法,我们实际上可以表示 cons
在程序方面。不受任何特定数据结构的影响。很酷,虽然令人困惑。
那么这种表示的应用有哪些?【参考方案3】:
这基本上是“闭包/对象同构”。
外部函数(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
【讨论】:
那么这种同构的应用是什么?以上是关于Scheme:返回另一个内部过程的过程的主要内容,如果未能解决你的问题,请参考以下文章
SQL SERVER里面如何在存储过程里面获取另一个存储过程所返回的表的数据?