关于尾递归函数的问题,例如这个和这个,但找不到类似于以下内容的内容。
我的理解是,尾部调用优化函数应该在其最后一次调用中返回累积值,而无需任何进一步的评估。例如,使用阶乘函数很容易理解,它被优化为循环 2。但是,在其他情况下并不总是很明显,例如在下文中,最后一个电话是什么?其中有很多,因为该函数在体内被递归调用不止一次。
布莱恩提出了一种找出答案的方法,但我不确定如何使其尾部调用优化。我可以将 --tailcalls
标志传递给编译器以自动执行此操作,但它总是成功吗?
f
和 g
返回相同的类型。
type T = T of int * T list
let rec myfunc f (T (x,xs)) =
if (List.isEmpty xs) then f x
else
List.fold g acc (List.map (fun xxs -> myfunc f xxs) xs)
任何帮助尾调用优化上述代码将不胜感激。
正如 Jon 已经说过的,你的函数不是尾递归的。基本问题是它需要多次递归调用自己(xs
列表中的每个元素一次,这是在传递给 List.map
的 lambda 函数中完成的)。
如果您确实需要进行多个递归调用,使用继续传递样式或命令式堆栈可能是唯一的选择。延续背后的想法是,每个函数都将采用另一个函数(作为最后一个参数),该函数应在结果可用时执行。
以下示例显示了普通版本(左侧)和基于延续的版本(右侧)
let res = foo a b fooCont a b (fun res ->
printfn "Result: %d" res printfn "Result: %d" res)
若要以延续传递样式编写函数,还需要使用基于延续的fold
函数。您可以首先通过将 map
中完成的操作移动到 fold
的 lambda 函数中来避免使用 map
:
List.fold g acc (List.map (fun xxs -> myfunc f xxs) xs)
成为:
List.fold (fun state xxs -> g state (myfunc f xxs)) acc xs
然后你可以按如下方式重写代码(请注意,你在问题中没有显示的f
和g
现在都是基于延续的函数,所以它们需要额外的参数,这表示延续):
// The additional parameter 'cont' is the continuation to be called
// when the final result of myfunc is computed
let rec myfunc' f (T (x,xs)) cont =
if (List.isEmpty xs) then
// Call the 'f' function to process the last element and give it the
// current continuation (it will be called when 'f' calculates the result)
f x cont
else
// Using the continuation-based version of fold - the lambda now takes current
// element 'xxs', the accumulated state and a continuation to call
// when it finishes calculating
List.foldCont (fun xxs state cont ->
// Call 'myfunc' recursively - note, this is tail-call position now!
myfunc' f xxs (fun res ->
// In the continuation, we perform the aggregation using 'g'
// and the result is reported to the continuation that resumes
// folding the remaining elements of the list.
g state res cont)) acc xs cont
List.foldCont
函数是基于延续的fold
版本,可以编写如下:
module List =
let rec foldCont f (state:'TState) (list:'T list) cont =
match list with
| [] -> cont state
| x::xs -> foldCont f state xs (fun state ->
f x state cont)
由于您没有发布完整的工作示例,因此我无法真正测试代码,但我认为它应该可以工作。
我的理解是,尾部调用优化函数应该在其最后一次调用中返回累积值......
几乎。尾递归是指递归调用全部出现在尾部位置。尾部位置表示调用方直接从其被调用方返回结果。
在下文中,最后一个电话是什么?
尾部位置有两个呼叫。首先,呼吁f
。第二,呼唤List.fold
。递归调用不在尾部位置,因为其调用方不直接返回其返回值。
if (List.isEmpty xs) then f x
此外,使用模式匹配而不是isEmpty
和朋友。
任何帮助尾调用优化上述代码将不胜感激。
你必须发布工作代码或至少一个规范,然后才能有人帮助你编写尾递归版本。通常,最简单的解决方案是以延续传递样式或命令式样式编写。