Functional programming 理解球拍中的移位/复位
我在racket中介绍了两个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
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
的“问题”实际上是它的主要特点让我们尝试实现
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
为例。