执行时这是一个尾调?

时间:2013-05-14 18:01:39

标签: f# tail-recursion tail-call

一旦编译并运行,这会表现为尾调用吗?

let rec f accu = function
   | [] -> accu
   | h::t -> (h + accu) |> f <| t

也许有一种简单的方法可以测试我不知道的行为,但这可能是另一个问题。

2 个答案:

答案 0 :(得分:6)

我认为如果您不使用流水线操作符,则更容易看到。实际上,两个流水线操作符定义为inline,因此编译器会将代码简化为以下内容(我认为这个版本更易读,更易于理解,所以我会写这个):

let rec f accu = function
   | [] -> accu
   | h::t -> f (h + accu) t

现在,您可以阅读tail-call on Wikipedia的定义,其中包含:

  

尾部调用是在另一个过程中发生的子程序调用,作为其最终操作;它可能会产生一个返回值,然后由调用过程立即返回。

所以是的,在最后一行调用f是一个尾调用。

如果您想分析原始表达式(h + accu) |> f <| t(不知道流水线运算符是内联的),那么这实际上变为((h + accu) |> f) <| t。这意味着表达式使用两个参数调用<|运算符并返回结果 - 因此对<|的调用是尾调用。

<|运算符的定义如下:

let (<|) f a = f a

现在,在流水线操作符内调用f也是一个尾调用(对于其他流水线操作符也是如此)。

总之,如果编译器没有进行内联,那么你将有一个三个尾调用序列(使用.NET .tail指令编译,以确保.NET执行尾调用)。但是,由于编译器执行内联,它将看到您有一个递归尾调用(f调用f),这可以更有效地编译成循环。 (但是跨多个函数或运算符的调用不能轻易使用循环。)

答案 1 :(得分:2)

如果您查看答案here,您会注意到f <| tf t相同(如果您将表达式替换为{{1},则只会有所不同这需要括号)。

同样tx |> y相同。

这导致一个等价的表达式,如下所示:y x,所以(假设编译器没有错误或某些错误),你的函数应该是尾递归的,并且最有可能被编译成某种循环。