如何实现尾递归列表追加?

mar*_*ngw 9 f# tail-recursion append

像这样的简单追加函数(在F#中):

let rec app s t =
   match s with
      | [] -> t
      | (x::ss) -> x :: (app ss t)
Run Code Online (Sandbox Code Playgroud)

当s变大时会崩溃,因为函数不是尾递归的.我注意到F#的标准追加功能不会因大列表而崩溃,因此必须以不同方式实现.所以我想知道:追尾的尾递归定义怎么样?我提出了这样的事情:

let rec comb s t =
   match s with
      | [] -> t
      | (x::ss) -> comb ss (x::t)
let app2 s t = comb (List.rev s) t 
Run Code Online (Sandbox Code Playgroud)

哪个有效,但看起来很奇怪.是否有更优雅的定义?

Jul*_*iet 26

传统(不是尾递归)

let rec append a b =
    match a, b with
    | [], ys -> ys
    | x::xs, ys -> x::append xs ys
Run Code Online (Sandbox Code Playgroud)

使用累加器(尾递归)

let append2 a b =
    let rec loop acc = function
        | [] -> acc
        | x::xs -> loop (x::acc) xs
    loop b (List.rev a)
Run Code Online (Sandbox Code Playgroud)

继续(尾递归)

let append3 a b =
    let rec append = function
        | cont, [], ys -> cont ys
        | cont, x::xs, ys -> append ((fun acc -> cont (x::acc)), xs, ys)
    append(id, a, b)
Run Code Online (Sandbox Code Playgroud)

它非常简单地将任何非尾递归函数转换为递归递归,但我个人更喜欢累加器以实现直接可读性.


Tom*_*cek 15

除了朱丽叶发布的内容:

使用序列表达式在
内部,序列表达式生成尾递归代码,因此这很好用.

let append xs ys = 
  [ yield! xs
    yield! ys ]
Run Code Online (Sandbox Code Playgroud)

使用可变的.NET类型
David提到F#列表可以变异 - 但是它仅限于F#核心库(并且该功能不能被用户使用,因为它打破了功能概念).您可以使用可变的.NET数据类型来实现基于突变的版本:

let append (xs:'a[]) (ys:'a[]) = 
  let ra = new ResizeArray<_>(xs)
  for y in ys do ra.Add(y)
  ra |> List.ofSeq
Run Code Online (Sandbox Code Playgroud)

这在某些情况下可能很有用,但我通常会避免F#代码中的突变.