F#tail递归,为什么不写一个while循环呢?

F#tail递归,为什么不写一个while循环呢?,f#,tail-recursion,F#,Tail Recursion,我正在学习F#(虽然多年来一直使用C#的函数方面,但对函数式编程来说是个新手,但事实上,这是非常不同的),我读到的一件事是F#编译器识别尾部递归并将其编译成while循环(请参阅) 我不明白的是,如果它将变成一个递归函数,为什么要编写一个递归函数而不是while循环。特别是考虑到您需要做一些额外的工作,使您的函数递归 我有一种感觉,有人可能会说while循环不是特别有功能的,你想表现出所有的功能性等等,所以你使用递归,但是为什么编译器把它变成while循环就足够了呢 有人能给我解释一下吗?递归函

我正在学习F#(虽然多年来一直使用C#的函数方面,但对函数式编程来说是个新手,但事实上,这是非常不同的),我读到的一件事是F#编译器识别尾部递归并将其编译成while循环(请参阅)

我不明白的是,如果它将变成一个递归函数,为什么要编写一个递归函数而不是while循环。特别是考虑到您需要做一些额外的工作,使您的函数递归

我有一种感觉,有人可能会说while循环不是特别有功能的,你想表现出所有的功能性等等,所以你使用递归,但是为什么编译器把它变成while循环就足够了呢


有人能给我解释一下吗?

递归函数通常是处理某些数据结构(如树和F#list)的最自然的方法。如果编译器出于性能原因想将我的自然、直观的代码转换成一个笨拙的while循环,那很好,但我为什么要自己编写呢

此外,布莱恩的观点与此相关。高阶函数通常可以替换代码中的循环和递归函数。

F#执行尾部优化的事实只是一个实现细节,它允许您以与while循环相同的效率使用尾部递归(并且不担心堆栈溢出)。但这只是一个实现细节,从表面上看,您的算法仍然是递归的,并且是以这种方式构造的,对于许多算法来说,这是表示它的最逻辑、最功能的方式

这同样适用于F#中的一些列表处理内部,内部变异用于更有效地实现列表操作,但程序员不知道这一事实


归根结底,这是语言如何允许您描述和实现算法,而不是在幕后使用什么机制来实现算法。

对于编译器执行的任何转换,您都可以使用相同的参数。例如,当您使用C#时,是否使用过lambda表达式或匿名委托?如果编译器只是将它们转换为类和(非匿名)委托,那么为什么不自己使用这些构造呢?同样,您是否使用过迭代器块?如果编译器只是想将这些转换成显式实现
IEnumerable
的状态机,那么为什么不自己编写代码呢?或者,如果C#编译器无论如何都要发出IL,那么为什么要费心编写C#而不是IL呢?等等

所有这些问题的一个明显答案是,我们想要编写代码,让我们能够清楚地表达自己。同样,有许多算法是自然递归的,因此编写递归函数通常会导致这些算法的清晰表达。特别是,在许多情况下,递归算法的终止比while循环更容易推理(例如,是否有明确的基本情况,以及每个递归调用是否使问题“更小”?)


然而,由于我们编写的是代码,而不是数学论文,因此拥有满足某些实际性能标准的软件也很不错(例如处理大型输入而不溢出堆栈的能力)。因此,尾部递归转换为while循环的等价物这一事实对于能够使用算法的递归公式是至关重要的。

while循环本质上是必需的。大多数情况下,当使用
while
循环时,您会发现自己编写的代码如下:

let mutable x = ...
...
while someCond do
    ...
    x <- ...
让可变x=。。。
...
有那么一秒钟
...

我不认为“尾部优化只是一个实现细节”。垃圾收集只是一个实现细节吗?如果关闭这两个选项之一,不仅会使某些程序运行变慢,还会导致堆栈溢出或内存不足异常而崩溃。知道某些程序只会消耗恒定的堆栈空间(参见heap)是非常有用的。我希望F#规范能对此做出一些保证。许多程序假定存在尾部调用消除(甚至更多的程序假定存在垃圾收集),否则就会中断。@petebu:同意-我的意思是F#如何执行尾部优化(即将其转换为while循环)并不重要,事实上,它确实是语言规范的一部分,因此某些程序需要它。另外值得注意的是,F#编译器并不总是将尾部递归函数转换为while循环。根据具体情况,它可能会这样做,或者它可能会发出
.tail
IL指令。您是否有关于规范在哪里保证关于tail调用的参考?我试图找到它(通过搜索“tail”),但找不到。@petebu:显然,那句话是错误的,这种优化不是语言的一部分,而是F#编译器的一个属性,我的坏-应该更仔细地阅读你的第一条评论;-)唯一的障碍是“处理某些数据结构的最自然方式”通常不是尾部递归。因此,我们只能将其转换为自己可读性较差的内容。但仍优于while循环。:-)@彼得布-这就是为什么我提到布莱恩的帖子。循环和递归函数都不是没有缺点的,这就是为什么
map
fold
可以这么好拥有的原因。也许是因为我对while循环感到更舒服,所以我问这个问题…@statichippo-是的,如果你发现while循环比递归更容易推理,那么你的问题是相当合理的。然而,随着更多地接触功能样式,您可能会看到递归更适合的情况。@ StasiChIPP-当然,您将更舒适地编写您所知道的,但请考虑这一点。在我开始c之前