一旦编译并运行,这会表现为尾调用吗?
let rec f accu = function
| [] -> accu
| h::t -> (h + accu) |> f <| t
也许有一种简单的方法可以测试我不知道的行为,但这可能是另一个问题。
答案 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 <| t
与f t
相同(如果您将表达式替换为{{1},则只会有所不同这需要括号)。
同样t
与x |> y
相同。
这导致一个等价的表达式,如下所示:y x
,所以(假设编译器没有错误或某些错误),你的函数应该是尾递归的,并且最有可能被编译成某种循环。