【问题标题】:Ocaml Tail recursive versionOcaml 尾递归版本
【发布时间】:2021-05-05 15:50:38
【问题描述】:

我有一个名为 zip_with_2_fs 的函数。我试图让它尾递归(zip_with_2_fs_tr),但我真的不明白我在做什么。我是 Ocaml 的新手,想深入了解尾递归和辅助函数。

let rec zip_with_2_fs fx fy xs ys =
 match (xs, ys) with
  | ([], []) -> []
  | ([], _) -> []
  | (_, []) -> []
  | (xh::xt, yh::yt) -> (fx xh, fy yh)::zip_with_2_fs fx fy xt yt;;

我将如何解决这个问题?

let zip_with_2_fs_tr fx fy xs ys =
  let rec helper fxx fy xss yss =
    match (xs, ys) with
    | ([], []) -> []
    | ([], _) -> []
    | (_, []) -> []
    | (xh::xt, yh::yt) -> (fx xh, fy yh)::helper fx fy xt yt
  in helper fx fy xs ys;;

【问题讨论】:

  • 您的问题是什么?你读过关于tail calls 的维基百科吗?您的zip_with_2_fs_tr 不是 尾递归的,因为:: 是在递归调用之后 完成的!注意continuation-passing style

标签: ocaml


【解决方案1】:

表达式(fx xh, fy yh)::helper fx fy xt yt 不是尾递归的,因为为了将其减少到一个值,您需要递归调用的结果。例如,让我们以更冗长的方式重写它,只是为了给事物命名(有名字的事物更容易谈论),

let head = (fx xh, fy yh) in
let tail = helper fx fy xt yt in (* recursive call *)
let result = head :: tail in
result

所以在这里,我们正在进行递归调用,并且当前调用的结果在此调用返回之前无法确定,因此我们的调用会累积(因为递归调用将进行自己的调用并等待结果准备好,并且等等),我们有宝贵的堆栈空间用于存储所有这些中间结果。

所以诀窍是递归调用返回的值应该是调用函数的结果。尾调用的概念实际上比尾递归调用更通用。由于任何调用结果成为调用者的结果的调用都称为尾调用,并且可以在不消耗堆栈空间的情况下进行调用,例如,说 C,

int bar(int x) {return x + 1; }
int foo(int x) {return bar(x+1);}

这里我们有一个从foo 到bar 的尾调用,编译器只需跳转到bar 即可轻松实现它,而无需分配新的堆栈帧或传递从bar.

回到 OCaml。所以我们需要确保我们的递归函数返回的值就是递归调用返回的值。但是这里没有免费的午餐,因为我们需要有一些空间来积累我们的中间结果。在非尾递归函数版本中,它是程序堆栈。我们可以在递归调用期间显式创建一个值并在其中累积值,而不是程序堆栈,例如,

let zip_with_f_fs fx fy xs ys =
  let rec loop acc xs ys = match xs, ys with
    | [],[] -> List.rev acc
    | x::xs,y::ys -> loop ((fx x, fy y)::acc) xs ys
    | _  -> failwith "uneven lists" in
  loop [] xs ys

现在你可以看到我们对loop进行了递归调用

    | x::xs,y::ys -> loop ((fx x, fy y)::acc) xs ys

那是在尾部位置,我们将结果存储在名为acc 的第一个参数中,一旦我们到达两个列表的末尾,我们就将其反转(因为我们假装它,实际上acc 是我们的stack) 以按正确的顺序获取值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-07
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多