F#尾递归,为什么不写一个while循环?
我正在学习F#(尽管函数式编程是新的,尽管C#使用了多年的功能方面,但让我们面对它,这是非常不同的),我读过的其中一个东西是F#编译器识别尾递归并编译它进入一个while循环(参见http://thevalerios.net/matt/2009/01/recursion-in-f-and-the-tail-recursion-police/)。
我不明白的是,为什么你会写一个递归函数而不是while循环,如果这就是它将会变成什么样子。 特别是考虑到你需要做一些额外的工作来使你的函数递归。
我有一种感觉,有人可能会说,while循环不是特别的功能,并且你想要执行所有的功能,所以你使用递归,但为什么它足以让编译器把它变成一个while循环?
谁可以给我解释一下这个?
您可以对编译器执行的任何转换使用相同的参数。 例如,当你使用C#时,你有没有使用lambda表达式或匿名代表? 如果编译器只是将它们转化为类和(非匿名)委托,那么为什么不自己使用这些构造呢? 同样,你有没有使用迭代器块? 如果编译器只是将这些转换为显式实现IEnumerable<T>
状态机,那么为什么不自己编写这些代码呢? 或者,如果C#编译器无论如何都会发射IL,那么为什么还要编写C#而不是IL呢? 等等。
所有这些问题的一个显而易见的答案是,我们希望编写能让我们清楚表达自己的代码。 同样,有许多算法是自然递归的,所以编写递归函数通常会导致这些算法的清晰表达。 特别是,在很多情况下,推断递归算法的终止可能比while循环更容易理解(例如,是否存在明确的基本情况,并且每次递归调用是否使问题“更小”?)。
然而,由于我们正在编写代码而不是数学论文,因此拥有符合某些真实世界性能标准(例如处理大量输入而不会溢出堆栈的能力)的软件也很好。 因此,尾递归转换为while循环等价的事实对于能够使用算法的递归公式是至关重要的。
递归函数通常是处理某些数据结构(如树和F#列表)的最自然的方式。 如果编译器想要将我的自然直观的代码转换为尴尬的while循环,但出于性能方面的原因,这很好,但为什么我要自己写这些?
另外,布赖恩对相关问题的回答在这里是相关的。 高阶函数通常可以代替代码中的循环和递归函数。
F#执行尾部优化的事实仅仅是一个实现细节,它允许您以相同的效率(而不用担心堆栈溢出)使用尾部递归作为while循环。 但它只是 - 一个实现细节 - 表面上你的算法仍然是递归的,并且是以这种方式构建的,对于许多算法来说,这是最合乎逻辑的功能性表示方法。
这同样适用于一些列表处理内部以及F# - 内部变异被用于更有效的列表操作实现,但这个事实对程序员来说是隐藏的。
它所涉及的是语言如何让你描述和实现你的算法,而不是使用什么机制来实现它。
链接地址: http://www.djcxy.com/p/14137.html