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
之后执行的,这意味着在函数返回后有事情要做,即compute
f(…,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
之后执行的,这意味着在函数返回后有事情要做,即compute
f(…,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