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))

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

【问题讨论】:

【参考方案1】:

这是在 Scheme 中利用一等函数的最奇怪的(也可能是更精彩的)示例之一。 Little Schemer 中也有类似的东西,这是我第一次看到它的地方,我记得为它挠了好几天头。让我看看我是否可以用有意义的方式解释它,但如果不清楚,我深表歉意。

我假设您理解原语 conscarcdr,因为它们已经在 Scheme 中实现,但只是提醒您:cons 构造一对,car 选择第一个组件该对并返回它,cdr 选择第二个组件并返回它。以下是使用这些函数的简单示例:

> (cons 1 2)
(1 . 2)
> (car (cons 1 2))
1
> (cdr (cons 1 2))
2

您粘贴的conscarcdr 版本的行为方式应该完全相同。我会试着告诉你怎么做。

首先,carcdr没有定义在cons的范围内。在您的代码 sn-p 中,所有三个(conscarcdr)都在顶层定义。函数dispatch 是唯一在cons 中定义的函数。

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

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

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

基于car的实现,它似乎是接受一个参数的函数,并将其应用于数字0。所以它将dispatch 应用到0,从dispatch 的定义可以看出,这就是我们想要的。里面的condm01 进行比较,并返回xy。在这种情况下,它返回x,这是cons 的第一个参数,也就是该对的第一个组件!所以car 选择第一个组件,就像Scheme 中的普通原语一样。

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

有几件事可以帮助您更好地理解这一点。一种是回到第 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 将是过程carcdr。真的吗?例如,我们评估(car (cons 1 2))。这将首先评估car参数,因此它将查看(cons 1 2)。这将落入dispatch....的定义中,它正在寻找m。那么dispatch怎么知道mconscar或者cdr呢?如果这个问题没有意义,请告诉我,我会尝试改写它:) 不,m 不是一个过程,它将是 01。当我们评估(car (cons 1 2)) 并放入cons 的主体时,内部函数dispatch 只是被定义然后返回,还没有被调用。所以那时我们并不关心m。然后当cons 返回时,我们将需要评估类似(car #&lt;function dispatch&gt;) 的内容。在car 的主体中,函数dispatch 绑定到z,并使用参数0 调用。所以dispatch 永远不知道carcdr,它只将它的参数m01 进行比较,所以它期待一个数字。 哦……很好,我想我明白了。它就像一个灯泡在我的脑海里亮着!哇......所以在(car (cons x y)) 中,car 评估它的参数 (cons),它本身返回一个名为 dispatch 的过程。 然后 car 应用于该过程。并且由于car 被定义为将其参数应用于0,并且 因为dispatchcons 的范围内定义,所以它能够解析为@ 的x 值987654416@。听起来对吗?我当然希望如此……哈哈。不过,还是会重读那一章! 是的,听起来你已经明白了! SICP 是一本非常棒的书,我很高兴能帮助其他程序员享受让他们的思想受到启发的乐趣。 你帮了我很多,希望其他程序员也一样。再次感谢!【参考方案2】:

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

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

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

这个例子真正好的部分是它显示了 cons-cell,Lisp 系统中最基本的 data 单元可以定义为 procedure ,因此模糊了 dataprocedure 之间的区别。

【讨论】:

谢谢!另一个答案更具描述性,并为我提供了书中的参考,但您的回答肯定也有助于我理解这个概念。尤其是 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里面如何在存储过程里面获取另一个存储过程所返回的表的数据?

SQL SERVER里面如何在存储过程里面获取另一个存储过程所返回的表的数据?

Oracle 存储过程调用返回游标的另一个存储过程。

调用另一个存储过程后从存储过程返回错误结果