Sml 为什么一个折叠尾是递归的而另一个不是?
为什么第一个是尾部递归的而另一个不是Sml 为什么一个折叠尾是递归的而另一个不是?,sml,tail-recursion,smlnj,Sml,Tail Recursion,Smlnj,为什么第一个是尾部递归的而另一个不是 我认为它们都是尾部递归的。第一个是尾部递归的,因为递归调用(对fold1)出现在函数体的末尾(它形成了“尾部”): 首先调用f(acc,hd),然后将结果(连同f和tl)传递到fold1。这是函数所做的最后一件事;递归fold1调用的结果传递给调用方 第二个不是尾部递归,因为递归调用(对foldl2)不是函数的最后一个操作: fold1 f (f (acc,hd)) tl 首先调用fold2 f acc tl,然后从结果和hd生成一个元组,然后将该元组传
我认为它们都是尾部递归的。第一个是尾部递归的,因为递归调用(对
fold1
)出现在函数体的末尾(它形成了“尾部”):
首先调用f(acc,hd)
,然后将结果(连同f
和tl
)传递到fold1
。这是函数所做的最后一件事;递归fold1
调用的结果传递给调用方
第二个不是尾部递归,因为递归调用(对
foldl2
)不是函数的最后一个操作:
fold1 f (f (acc,hd)) tl
首先调用fold2 f acc tl
,然后从结果和hd
生成一个元组,然后将该元组传递给f
递归
fold2
调用后会发生两件事:元组构造((…,hd)
)和另一个函数调用(f…
)。特别是,调用fold2
的结果不会直接传递给调用方。这就是为什么此代码不是尾部递归的原因。第一个代码是尾部递归的,因为递归调用(对fold1
)出现在函数体的末尾(它形成了“尾部”):
首先调用f(acc,hd)
,然后将结果(连同f
和tl
)传递到fold1
。这是函数所做的最后一件事;递归fold1
调用的结果传递给调用方
第二个不是尾部递归,因为递归调用(对
foldl2
)不是函数的最后一个操作:
fold1 f (f (acc,hd)) tl
首先调用fold2 f acc tl
,然后从结果和hd
生成一个元组,然后将该元组传递给f
递归fold2
调用后会发生两件事:元组构造((…,hd)
)和另一个函数调用(f…
)。特别是,调用fold2
的结果不会直接传递给调用方。这就是为什么这段代码不是尾部递归的
为什么第一个是尾部递归的而另一个不是
给定一个
如果函数返回后除了返回其值之外无需执行任何操作,则称函数调用为尾部递归
在第一个函数中
f (fold2 f acc tl, hd)
fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
所有其他计算(f(acc,hd)
)都是作为fold1
的参数执行的,这意味着函数返回后除了返回其值外,没有其他操作
在第二个函数中
f (fold2 f acc tl, hd)
fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
所有其他计算(f(…,hd)
)都是在执行了fold2 f acc tl
之后执行的,这意味着在函数返回后有事情要做,即computef(…,hd)
尾部递归函数的定义特征是,其递归函数体的最外层表达式是对自身的调用。如果在函数中计算了任何其他内容,则应在进行此调用之前进行,例如作为函数参数或let绑定
例如,fold1
的以下重构也是尾部递归的:
fun fold2 f acc lst =
case lst of
[] => acc
| hd::tl => f (fold2 f acc tl, hd)
下面的fold2
重构不是:
fun fold1 f acc0 [] = acc0
| fold1 f acc0 (x::xs) =
let val acc1 = f (acc0, x)
in fold1 f acc1 xs
end
因为在acc1 xsfold1之后没有其他事情要做,所以可以安全地丢弃函数调用的上下文(堆栈帧)。非尾部递归与尾部递归的简单示例如下:
fun fold2 f acc0 [] = acc0
| fold2 f acc0 (x::xs) =
let val acc1 = fold2 f acc0 xs
in f (acc1, x)
end
为什么第一个是尾部递归的而另一个不是
给定一个
如果函数返回后除了返回其值之外无需执行任何操作,则称函数调用为尾部递归
在第一个函数中
f (fold2 f acc tl, hd)
fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
所有其他计算(f(acc,hd)
)都是作为fold1
的参数执行的,这意味着函数返回后除了返回其值外,没有其他操作
在第二个函数中
f (fold2 f acc tl, hd)
fun fold1 f acc lst =
case lst of
[] => acc
| hd::tl => fold1 f (f (acc,hd)) tl
所有其他计算(f(…,hd)
)都是在执行了fold2 f acc tl
之后执行的,这意味着在函数返回后有事情要做,即computef(…,hd)
尾部递归函数的定义特征是,其递归函数体的最外层表达式是对自身的调用。如果在函数中计算了任何其他内容,则应在进行此调用之前进行,例如作为函数参数或let绑定
例如,fold1
的以下重构也是尾部递归的:
fun fold2 f acc lst =
case lst of
[] => acc
| hd::tl => f (fold2 f acc tl, hd)
下面的fold2
重构不是:
fun fold1 f acc0 [] = acc0
| fold1 f acc0 (x::xs) =
let val acc1 = f (acc0, x)
in fold1 f acc1 xs
end
因为在acc1 xsfold1之后没有其他事情要做,所以可以安全地丢弃函数调用的上下文(堆栈帧)。非尾部递归与尾部递归的简单示例如下:
fun fold2 f acc0 [] = acc0
| fold2 f acc0 (x::xs) =
let val acc1 = fold2 f acc0 xs
in f (acc1, x)
end
TL;博士
如果是尾部递归,它必须位于尾部位置。参数不是尾部位置。
就你而言:
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
e1
和e2
都是尾部,因为在计算这两个表达式的值后,没有其他表达式(注意,它们位于尾部位置)。这就是为什么fold1
是尾部递归
至于f(fold2 f acc tl,hd)
,f(…)
确实位于尾部位置。但是这里的fold2
不在尾部位置,因为在计算了它的值之后,仍然需要调用f
。因此f
是尾部递归,而fold2
不是;博士
如果是尾部递归,它必须位于尾部位置。参数不是尾部位置。
就你而言:
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
e1
和e2
都是尾部,因为在计算这两个表达式的值后,没有其他表达式(I