fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
fun fold2 f acc lst =
case lst of
[] => acc
| hd::tl => f (fold2 f acc tl, hd)
为什么第一个是尾递归的而另一个不是?
我认为它们都是尾递归的。
答案 0 :(得分:3)
第一个是尾递归,因为(对fold1
的递归调用出现在函数体的末尾(形成“尾巴”):
fold1 f (f (acc,hd)) tl
首先调用f (acc,hd)
,然后将结果(以及f
和tl
)传递到fold1
。这是函数要做的最后一件事。递归fold1
调用的结果将传递给我们的调用者。
第二个不是尾递归,因为(对foldl2
的递归调用不是该函数执行的最后操作:
f (fold2 f acc tl, hd)
首先调用fold2 f acc tl
,然后根据结果创建一个元组,然后hd
,然后将该元组传递给f
。
在递归fold2
调用之后发生了两件事:元组构造((..., hd)
)和另一个函数调用(f ...
)。特别是,调用fold2
的结果不会直接传递给我们的调用者。这就是为什么这段代码不是尾递归的。
答案 1 :(得分:0)
为什么第一个是尾递归的而另一个不是?
给出一个definition of tail-recursive,
如果函数返回后除返回其值外无事可做,则称该函数调用为尾递归。
在第一个功能中,
fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
所有其他计算(f (acc, hd)
)作为fold1
的参数执行,这意味着该函数返回后除返回其值外没有其他操作。
在第二个功能中,
fun fold2 f acc lst =
case lst of
[] => acc
| hd::tl => f (fold2 f acc tl, hd)
所有其他计算(f (..., hd)
)是在执行完 fold2 f acc tl
之后执行的,这意味着在该函数之后需要执行 返回,即计算f (..., hd)
。
尾递归函数具有定义特征,即其递归函数主体的最外层表达式是对自身的调用。如果函数中计算出其他任何内容,则应在调用之前 发生,例如作为函数参数或在let绑定中。
例如,fold1
的以下重构也是尾递归的:
fun fold1 f acc0 [] = acc0
| fold1 f acc0 (x::xs) =
let val acc1 = f (acc0, x)
in fold1 f acc1 xs
end
以及以下fold2
的重构不是:
fun fold2 f acc0 [] = acc0
| fold2 f acc0 (x::xs) =
let val acc1 = fold2 f acc0 xs
in f (acc1, x)
end
由于fold1 f acc1 xs
之后没有其他事情要做,因此可以安全地丢弃函数调用的上下文(堆栈框架)。非尾递归与尾递归的一个简单示例是:
fun length [] = 0
| length (_::xs) = 1 + length xs
fun length xs =
let fun go [] count = count
| go (_::ys) count = go ys (1 + count)
in go xs 0
end
答案 2 :(得分:0)
您的情况:
case lst of
[] => e1
| hd::tl => e2
e1
和e2
都是尾写形式,因为在评估两个表达式的 value 后没有其他表达式 (IOW,它们位于尾部位置)。这就是fold1
是尾递归的原因。
对于f (fold2 f acc tl, hd)
,f(...)
确实位于尾巴位置。但是fold2
不在尾部,因为在评估其值之后,您仍然需要调用f
。因此f
是尾递归,而fold2
不是尾递归。