了解Racket中的移位/重置

时间:2016-10-02 18:34:06

标签: functional-programming racket continuations delimited-continuations

我在球拍

中展示了foldr的两个天真实现

第一个缺少正确的尾调用,并且对于xs

的大值有问题
(define (foldr1 f y xs)
  (if (empty? xs)
      y
      (f (car xs) (foldr1 f y (cdr xs)))))

(foldr1 list 0 '(1 2 3))
; => (1 (2 (3 0))

第二个使用带有延续的辅助函数来实现正确的尾调用,使其可以安全地用于xs的大值

(define (foldr2 f y xs)
  (define (aux k xs)
    (if (empty? xs)
        (k y)
        (aux (lambda (rest) (k (f (car xs) rest))) (cdr xs))))
  (aux identity xs))

(foldr2 list 0 '(1 2 3))
; => (1 (2 (3 0)))

racket/control我看到球拍支持一流的延续。我想知道使用foldrshift表达reset的第二个实现是否可行/有益。我正在玩它一会儿,我的大脑刚刚转过来。

请提供详尽的解释。我在这里寻求全局了解。

1 个答案:

答案 0 :(得分:1)

免责声明:

  1. 您要解决的foldr的“问题”实际上是其主要特征。
  2. 从根本上讲,您不能轻松地对列表进行反向处理,而最好的办法就是先对其进行反向处理。从本质上讲,您使用lambda的解决方案与递归没有什么不同,只是您没有在堆栈上累积递归调用,而是在许多lambda中显式累积了它们,因此,唯一的好处是,它不受堆栈的限制大小,您可以使用尽可能多的内存,因为很可能在堆上分配了lambda,并且需要权衡的是,现在您为每个“递归调用”执行动态内存分配/取消分配。 < / li>

现在,不用担心了,到了实际的答案。


让我们尝试实现foldr的同时牢记我们可以继续使用。这是我的第一次尝试:

(define (foldr3 f y xs)
  (if (empty? xs)
    y
    (reset 
      (f (car xs) (shift k (k (foldr3 f y (cdr xs))))))))
  ; ^ Set a marker here.
  ;    ^ Ok, so we want to call `f`.
  ;               ^ But we don’t have a value to pass as the second argument yet.
  ;                 Let’s just pause the computation, wrap it into `k` to use later...
  ;                 And then resume it with the result of computing the fold over the tail.

如果仔细看一下这段代码,您会发现它与您的foldr完全相同–即使我们“暂停”了计算,我们也会立即恢复它并传递递归结果调用它,这种构造当然不是尾递归的。

好,那么看来我们需要确保不要立即恢复它,而是先执行递归计算,然后然后用递归计算结果恢复已暂停的计算。让我们重新设计一下我们的函数以接受延续,并在它实际计算出所需的值后调用它。

(define (foldr4 f y xs)
  (define (aux k xs)
    (if (empty? xs)
      (k y)
      (reset
        (k (f (car xs) (shift k2 (aux k2 (cdr xs))))))))
  (reset (shift k (aux k xs))))

这里的逻辑与先前的版本相似:在if的非平凡分支中,我们设置了reset标记,然后开始计算表达式,就好像我们拥有了所需的一切一样;但是,实际上,我们还没有列表尾部的结果,因此我们暂停计算,将其“打包”到k2中,然后执行(这次是尾部)递归调用,说“嘿,得到结果后,请恢复暂停的计算。”

如果您分析这段代码的执行方式,您会发现其中绝对没有魔术,它的工作原理是,在遍历列表时将连续的一个“包裹”到另一个,然后到达终点,延续将被“解开”并以相反的顺序一一执行。实际上,此功能与foldr2的功能完全相同–区别只是语法上的:reset / shift模式允许我们开始写出立即表达,然后在某个时候说“稍等片刻,我还没有这个值,让我们在这里暂停并稍后返回” ...但是在幕后,它最终创建了与{{1} }!

我相信,列表无法比这做得更好。

另一个免责声明:我没有实现lambda / reset的有效Scheme / Rack解释器,因此我没有测试功能。