开发者

Understanding F# tail-recursive

开发者 https://www.devze.com 2023-01-01 12:52 出处:网络
Recently, I\'m learning F#. I try to solve problem in different ways. Like this: (* [0;1;2;3;4;5;6;7;8] -> [(0,1,2);(3,4,5);(6,7,8)]

Recently, I'm learning F#. I try to solve problem in different ways. Like this:

(*

[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 lo开发者_运维问答op 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)

I thought the results of V2 and V3 should be the same. But, I get the result below:

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)]

Why the results of V2 and V3 are different?


V2 uses standard accumulating variable to get tail recursion done:

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 uses continuation, or in plain English, an accumulating function:

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)]

You can see the difference between accumulating variables and accumulating functions:

Using accumulating variables stops at the last call as the accumulating variable stores the answer. However, the accumulating function still does some backtrack work after the last call. It should be noticed that using accumulating functions is indeed tail recursive as the recursive call loop t (fun ls -> accfun ((a,b,c)::ls)) is actually the last statement of this function.

Btw, the code you provided is a good example to show the concept tail recursive functions. One way to understand these sample code is to work on small cases as I did in the above two illustrations. After working on some small cases, you will understand the concept deeper.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号