此 F# 函数尾递归,其中递归函数在函数内被多次调用


有几个

关于尾递归函数的问题,例如这个和这个,但找不到类似于以下内容的内容。

我的理解是,尾部调用优化函数应该在其最后一次调用中返回累积值,而无需任何进一步的评估。例如,使用阶乘函数很容易理解,它被优化为循环 2。但是,在其他情况下并不总是很明显,例如在下文中,最后一个电话是什么?其中有很多,因为该函数在体内被递归调用不止一次。

布莱恩提出了一种找出答案的方法,但我不确定如何使其尾部调用优化。我可以将 --tailcalls 标志传递给编译器以自动执行此操作,但它总是成功吗?

fg 返回相同的类型。

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

然后你可以按如下方式重写代码(请注意,你在问题中没有显示的fg现在都是基于延续的函数,所以它们需要额外的参数,这表示延续):

// 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和朋友。

任何帮助尾调用优化上述代码将不胜感激。

你必须发布工作代码或至少一个规范,然后才能有人帮助你编写尾递归版本。通常,最简单的解决方案是以延续传递样式或命令式样式编写。

最新更新