f# - 了解 F# 尾递归

标签 f# tail-recursion

最近,我正在学习 F#。
我尝试以不同的方式解决问题。
像这样:

(*

[0;1;2;3;4;5;6;7;8] -> [(0,1,2);(3,4,5);(6,7,8)]

*)

//head-recursive
let rec toTriplet_v1 list=
    match list with
    | a::b::c::t -> (a,b,c)::(toTriplet_v1 t) 
    | _ -> []

//tail-recursive
let toTriplet_v2 list=
    let rec loop lst acc=
        match lst with
        | a::b::c::t -> loop t ((a,b,c)::acc)
        | _ -> acc
    loop list []

//tail-recursive(???)
let toTriplet_v3 list=
    let rec loop lst accfun=
        match lst with
        | a::b::c::t -> loop t (fun ls -> accfun ((a,b,c)::ls))
        | _ -> accfun []
    loop list (fun x -> x)

let funs = [toTriplet_v1; toTriplet_v2; toTriplet_v3];
funs |> List.map (fun x -> x [0..8]) |> List.iteri (fun i x -> printfn "V%d : %A" (i+1) x)

我认为V2和V3的结果应该是一样的。
但是,我得到以下结果:
V1 : [(0, 1, 2); (3, 4, 5); (6, 7, 8)]
V2 : [(6, 7, 8); (3, 4, 5); (0, 1, 2)]
V3 : [(0, 1, 2); (3, 4, 5); (6, 7, 8)]

为什么V2和V3的结果不同?

最佳答案

V2使用标准累积变量完成尾递归:

loop ([0;1;2;3;4;5;6;7;8], []) ->
  loop ([3;4;5;6;7;8], [(0,1,2)]) ->
    loop ([6;7;8], [(3,4,5), (0,1,2)]) ->
      loop ([], [(6,7,8), (3,4,5), (0,1,2)]) ->
        [(6,7,8), (3,4,5), (0,1,2)]

V3 使用 continuation ,或者用简单的英语,一个 累加函数 :
loop ([0;1;2;3;4;5;6;7;8], x->x) ->
  loop ([3;4;5;6;7;8], x->(0;1;2)::x) ->
    loop ([6;7;8], x->(3;4;5)::x) ->
      loop ([], x->(6,7,8)::x) ->
        [(6,7,8)]  // x->(6,7,8)::x applies to []
    ->
      [(3,4,5);(6,7,8)] // x->(3,4,5)::x applies to [(6,7,8)]
  ->
    [(0,1,2);(3,4,5);(6,7,8)] // x->(0,1,2)::x applies to [(3,4,5);(6,7,8)]

您可以看到累加变量和累加函数之间的区别:

使用累加变量在最后一次调用时停止,因为累加变量存储了答案。但是,累加函数仍然做了一些回溯在最后一次通话后工作。需要注意的是,使用累加函数确实是尾递归,因为递归调用loop t (fun ls -> accfun ((a,b,c)::ls))实际上是 最后 这个函数的声明。

顺便说一句,您提供的代码是展示尾递归函数概念的一个很好的例子。理解这些示例代码的一种方法是 处理小案例 就像我在上面两个插图中所做的那样。在处理过一些小案例之后,你会更深入地理解这个概念。

关于f# - 了解 F# 尾递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2948205/

相关文章:

f# - 跨 F# .fsx 文件共享类型

f# - 可以在 F# 中自定义 Brace Matching 颜色吗?

optimization - FPL 中的尾调用优化是如何在汇编级别实现的?

scala - 有没有办法关闭Scala编译器的尾递归优化?

python - python堆栈是否随着递归过程执行的迭代过程而增长?

compiler-construction - 在F#中使用Roslyn

f# - 为什么我不能在 F# 中使用最新版本的 NUnit 和 FsCheck?

.net - F# 比较区分联合的案例标识符

lisp - 哪个是尾递归?

clojure - 在Clojure中使用递归时溢出