免责声明:
foldr
你们要解决的“问题”其实是它的主要特点。
- 从根本上说,您无法轻松地反向处理列表,您能做的最好的事情就是先将其反向。从本质上讲,您使用 lambda 的解决方案与递归没有什么不同,只是不是在堆栈上累积递归调用,而是在许多 lambda 中显式地累积它们,所以唯一的好处是不受堆栈的限制大小,你可以尽可能多地使用你拥有的内存,因为 lambda 很可能是在堆上分配的,并且权衡是你现在为每个“递归调用”执行动态内存分配/释放。
现在,有了这个,到了实际的答案。
让我们尝试实现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
——区别只是语法上的:不是创建显式的 lambda,而是reset
/shift
模式允许我们立即开始写出表达式,然后在某个时候说“等一下,我还没有这个值,让我们在这里暂停并稍后返回”......但在引擎盖下它最终lambda
会创建相同的闭包!
我相信,没有比这更好的列表了。
另一个免责声明:我没有实现 / 的有效 Scheme/Racket 解释器reset
,shift
所以我没有测试这些功能。