Functional programming 理解球拍中的移位/复位

Functional programming 理解球拍中的移位/复位,functional-programming,racket,continuations,delimited-continuations,Functional Programming,Racket,Continuations,Delimited Continuations,我在racket中介绍了两个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)) (define (foldr2 f y xs) (define (aux k xs) (if (empty? xs

我在racket中介绍了两个
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))
(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)))
第二个函数使用了一个带有延续符的辅助函数来实现正确的尾部调用,使得它可以安全地用于
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))
(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)))
查看
球拍/控制
我发现球拍支持一流的连续性。我想知道使用
shift
reset
表达
foldr
的第二个实现是否可能/有益。我和它玩了一会儿,结果我的大脑翻了个底朝天

请提供详尽的解释和任何答案。我希望在这里了解全局。

免责声明:

  • 您试图解决的
    foldr
    的“问题”实际上是它的主要特点
  • 从根本上说,您无法轻松地反向处理列表,您最好先将其反向处理。从本质上讲,使用lambda的解决方案与递归没有什么不同,只是没有在堆栈上累积递归调用,而是在许多lambda中显式地累积它们,因此唯一的好处是,不受堆栈大小的限制,您可以尽可能深地使用内存,因为lambda很可能,在堆上进行分配,取舍是现在为每个“递归调用”执行动态内存分配/释放
  • 现在,让我们来看看真正的答案


    让我们尝试实现
    foldr
    ,记住我们可以使用continuations。这是我的第一次尝试:

    (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
    完全相同–即使我们“暂停”计算,我们也会立即恢复计算并将递归调用的结果传递给它,当然,这种构造不是尾部递归的

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

    (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
    的作用完全相同–区别仅仅是语法上的:与其创建显式lambda,不如使用
    reset
    /
    shift
    模式允许我们立即开始写出表达式,然后在某个时候说“等一下,我还没有这个值,让我们暂停一下,稍后再返回”……但在引擎盖下,它最终创建了与
    lambda
    相同的闭包

    我相信,没有比这更好的方法了


    另一个免责声明:我没有一个执行了
    reset
    /
    shift
    的工作模式/Racket解释器,所以我没有测试功能。

    第一个看起来与Racket中提供的相同。如果Racket不需要使它复杂化,为什么需要?@sylvester我不想集中精力在
    foldr
    上tself,但更多的是关于如何使用不同的技术通过适当的尾部调用来表达过程。我只是以
    foldr
    为例。