帮助理解 Scheme 中的 Continuations
Posted
技术标签:
【中文标题】帮助理解 Scheme 中的 Continuations【英文标题】:Help understanding Continuations in Scheme 【发布时间】:2011-01-02 08:17:29 【问题描述】:我一直在与 The Little Schemer 一起学习 Scheme 并在我的环境中使用 PLT-Scheme。
The Little Schemer 极大地帮助了我的递归(现在对我来说很简单),但我被困在书中介绍“收集器”并将函数作为一个整体调用的部分继续。
这是他们使用的示例代码。我理解递归元素,但我被困住了,特别是在 lambda 函数上——我的头脑无法遵循路径以及该 lambda 函数的参数是如何设置的(因为它们唯一的调用是在递归中再次调用它们,所以有在函数体内没有具体的使用)。
如果有人可以通过将函数递归到 lambda 收集器中或多或少地给我分解计算路径,那可能会对我有所帮助。
;; Build a nested list of even numbers by removing the odd ones from its
;; argument and simultaneously multiply the even numbers and sum the odd
;; numbers that occur in its argument.
(define (even-only-collector l col)
(cond
((null? l)
(col (quote ()) 1 0))
((atom? (car l))
(cond
((even? (car l))
(even-only-collector (cdr l)
(lambda (newl p s)
(col (cons (car l) newl)
(* (car l) p) s))))
(else
(even-only-collector (cdr l)
(lambda (newl p s)
(col newl
p (+ (car l) s)))))))
(else
(even-only-collector (car l)
(lambda (al ap as)
(even-only-collector (cdr l)
(lambda (dl dp ds)
(col (cons al dl)
(* ap dp)
(+ as ds)))))))))
;; The collector function
(define (collector newl product sum)
(cons sum
(cons product newl)))
提前谢谢你!!
【问题讨论】:
@lpthnc:你看过 newLISP 吗? 【参考方案1】:尝试一些更简单的方法来看看它是如何工作的。例如,下面是一个接收延续参数的list-sum
函数版本(通常称为k
):
(define (list-sum l k)
(if (null? l)
???
(list-sum (cdr l) ???)))
基本模式就在那里,而缺少的部分就是有趣的事情发生的地方。 continuation 参数是一个期望接收结果的函数——所以如果列表为空,很明显我们应该发送它0
,因为这是总和:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) ???)))
现在,当列表不为空时,我们使用列表的尾部递归调用函数(换句话说,这是一个迭代),但问题是延续应该是什么。这样做:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) k)))
显然是错误的——这意味着k
最终将收到(cdr l)
的总和,而不是所有l
。取而代之的是,在那里使用一个新函数,它将把l
的第一个元素连同它接收到的值相加:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (lambda (sum) (+ (car l) sum)))))
这越来越近了,但仍然是错误的。但是最好考虑一下事情是如何工作的——我们调用list-sum
并带有一个本身将接收总和的延续,并将我们现在看到的第一个项目添加到其中。在我们忽略k
这一事实中,缺失的部分很明显。我们需要的是用这个函数composek
——所以我们做同样的求和运算,然后将结果发送到k
:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (compose k (lambda (s) (+ s (car l)))))))
终于奏效了。 (顺便说一句,请记住,每个 lambda
函数都有自己的 l
的“副本”。)你可以试试这个:
(list-sum '(1 2 3 4) (lambda (x) x))
最后请注意,这与以下内容相同:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (lambda (s) (k (+ s (car l)))))))
如果你明确组合。
(您也可以在中级 + lambda 学生语言中使用此代码,然后单击步进按钮以查看评估如何进行 - 这将需要一段时间才能完成,但您会看到延续函数如何获得嵌套,每个都有自己的列表视图。)
【讨论】:
非常感谢,这就是我一直在寻找的答案——步进器的提示特别有用。谢谢! 我刚刚和中级学生一起用 lambda 语言和步进器运行了你的练习;我无法告诉你这有多大帮助。能够以这种方式看到执行路径消除了我所有的困惑!非常感谢。 非常有帮助,Eli,谢谢。我不知道学生语言和步进器,非常好。 @EliBarzilay,谢谢。我想知道在这里使用延续有什么好处?这应该可行,并且看起来更简单:(define list-sum (lambda (l) (cond ((null? l) 0) (else (+ (car l) (list-sum (cdr l))))))) (list-sum '(1 2 3 4))
@liweijian:这里没有真正的好处,只是一个演示。【参考方案2】:
这里有一种方法可以帮助您“获得更具体的想法”。想象一下如果收集器是这样定义的:
(define (collector l p s)
(display l)
(newline)
(display p)
(newline)
(display s)
(newline))
您可以在基本情况下看到,如果您传入一个空列表,它将使用参数'()
、1 和 0 调用您的函数。现在,使用单元素列表,看看它是什么会调用你的函数。继续处理越来越长的列表,直到弄清楚发生了什么。
祝你好运!
【讨论】:
所以,我完全理解传入一个空列表时会发生什么,并且我可以看到当我传入越来越小的列表时会发生什么;但我并没有掌握那些拥有收集器的 lambda 表达式的“如何”...... 您曾经使用过面向对象的语言吗?如果是这样,你听说过装饰器模式吗?每个 lambdas 基本上都是用一层装饰你的收集器。 我有,但仅限于 php 和 一些 Python。我听说过 Python 中的装饰器,但从未真正研究过它们。那么这个函数在技术上也称为延续吗?以上是关于帮助理解 Scheme 中的 Continuations的主要内容,如果未能解决你的问题,请参考以下文章
x 和 x 的集合在 Scheme 中的 let 中不起作用