如何使这个 Scheme 函数不尾递归?
How to make this Scheme function not tail recursive?
我不知道如何让这个尾递归 Scheme 函数不再是尾递归。谁能帮帮我?
(define (foldrecl f x u)
(if (null? x)
u
(foldrecl f (cdr x) (f (car x) u))))
left 折叠是继承迭代的,但您可以通过添加延续轻松地使它们递归。例如。
(let ((value expresion-that-calculates))
value)
所以在你的情况下:
(define (foldrecl f x u)
(if (null? x)
u
(let ((result (foldrecl f (cdr x) (f (car x) u))))
result)))
虽然这看起来很有希望,但它并不能保证智能 Scheme 实现会发现 result
只是返回并使其成为尾调用。右折叠更容易,因为它们本质上是递归的:
(define (fold-right proc tail lst)
(if (null? lst)
tail
(proc (car lst)
(fold-right proc tail (cdr lst)))))
在这里你清楚地看到递归部分需要成为 cons
的参数,因此永远不会在尾部位置,除非它是基本情况。
另请注意,在调用过程 proc
、结果的尾部 tail
和列表参数 lst
时,查看参数的位置稍微简单一些。你甚至不需要阅读我的代码就知道如何使用它,但你的我不知道什么是 x
和 u
并且 ti 没有帮助,因为参数顺序不遵循任何fold
Scheme 中已知的实现。
递归调用在尾部位置,所以将它放在另一个过程调用中,如下所示:
(define (identity x) x)
(define (foldrecl f x u)
(if (null? x)
u
(identity (foldrecl f (cdr x) (f (car x) u)))))
现在递归调用不在尾部位置,不再是尾递归了。
如果编译器知道它什么都不做但希望它不会,则允许编译器优化身份函数。
与其做,不如写一个计划去做;只到最后,do:
(define (foldreclr f xs a)
(define (go xs)
(if (null? xs)
(lambda (a) a)
(let ((r (go (cdr xs)))) ; first, recursive call;
(lambda ; afterwards, return a plan:
(a) ; given an a, to
(r ; perform the plan for (cdr xs)
(f (car xs) a)))))) ; AFTER processing (car x) and a.
((go xs) ; when the overall plan is ready,
a)) ; use it with the supplied value
内部函数go
遵循右折叠模式。它首先进行递归调用,然后才组成 returns 一个值,计划 first 将列表的头元素与 accumulator 值,然后 然后 执行列表尾部的计划——就像原来的 foldrecl
会做的那样。
当整个列表变成行动计划时,最终执行该行动以转换提供的初始累加器值——执行与原始 foldrecl
左折叠相同的计算。
这就是所谓的向右倾斜那么远你又会向左倾斜。(*)
> (foldreclr - (list 1 2 3 4) 0) ; 4-(3-(2-(1-0)))
2
> (foldreclr - (list 4 3 2 1) 0) ; 1-(2-(3-(4-0)))
-2
另请参阅:
- Foldl as foldr
- (*) Evolution of a Haskell programmer(有趣的阅读)
(抱歉,这些在 Haskell 中,但 Haskell 也是 Lisp。)
我不知道如何让这个尾递归 Scheme 函数不再是尾递归。谁能帮帮我?
(define (foldrecl f x u)
(if (null? x)
u
(foldrecl f (cdr x) (f (car x) u))))
left 折叠是继承迭代的,但您可以通过添加延续轻松地使它们递归。例如。
(let ((value expresion-that-calculates))
value)
所以在你的情况下:
(define (foldrecl f x u)
(if (null? x)
u
(let ((result (foldrecl f (cdr x) (f (car x) u))))
result)))
虽然这看起来很有希望,但它并不能保证智能 Scheme 实现会发现 result
只是返回并使其成为尾调用。右折叠更容易,因为它们本质上是递归的:
(define (fold-right proc tail lst)
(if (null? lst)
tail
(proc (car lst)
(fold-right proc tail (cdr lst)))))
在这里你清楚地看到递归部分需要成为 cons
的参数,因此永远不会在尾部位置,除非它是基本情况。
另请注意,在调用过程 proc
、结果的尾部 tail
和列表参数 lst
时,查看参数的位置稍微简单一些。你甚至不需要阅读我的代码就知道如何使用它,但你的我不知道什么是 x
和 u
并且 ti 没有帮助,因为参数顺序不遵循任何fold
Scheme 中已知的实现。
递归调用在尾部位置,所以将它放在另一个过程调用中,如下所示:
(define (identity x) x)
(define (foldrecl f x u)
(if (null? x)
u
(identity (foldrecl f (cdr x) (f (car x) u)))))
现在递归调用不在尾部位置,不再是尾递归了。
如果编译器知道它什么都不做但希望它不会,则允许编译器优化身份函数。
与其做,不如写一个计划去做;只到最后,do:
(define (foldreclr f xs a)
(define (go xs)
(if (null? xs)
(lambda (a) a)
(let ((r (go (cdr xs)))) ; first, recursive call;
(lambda ; afterwards, return a plan:
(a) ; given an a, to
(r ; perform the plan for (cdr xs)
(f (car xs) a)))))) ; AFTER processing (car x) and a.
((go xs) ; when the overall plan is ready,
a)) ; use it with the supplied value
内部函数go
遵循右折叠模式。它首先进行递归调用,然后才组成 returns 一个值,计划 first 将列表的头元素与 accumulator 值,然后 然后 执行列表尾部的计划——就像原来的 foldrecl
会做的那样。
当整个列表变成行动计划时,最终执行该行动以转换提供的初始累加器值——执行与原始 foldrecl
左折叠相同的计算。
这就是所谓的向右倾斜那么远你又会向左倾斜。(*)
> (foldreclr - (list 1 2 3 4) 0) ; 4-(3-(2-(1-0)))
2
> (foldreclr - (list 4 3 2 1) 0) ; 1-(2-(3-(4-0)))
-2
另请参阅:
- Foldl as foldr
- (*) Evolution of a Haskell programmer(有趣的阅读)
(抱歉,这些在 Haskell 中,但 Haskell 也是 Lisp。)