帮助理解 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 中不起作用

如何在MIT / GNU Scheme中读取文本文件?

关于 Scheme (plt-scheme) 中的“If..”

Scheme - 嵌套定义混淆

为啥让首选在Scheme中定义?

从Scheme(MIT / GNU Scheme)中的文件中读取行