什么时候需要显式递归?

Posted

技术标签:

【中文标题】什么时候需要显式递归?【英文标题】:When is explicit recursion necessary? 【发布时间】:2015-01-13 17:46:55 【问题描述】:

在 Haskell 中,将尽可能多的代码写入高阶函数(如折叠、映射和展开)中是惯用的做法。那么什么样的代码不能用那些高阶函数编写呢?

【问题讨论】:

您是在问什么时候递归是必要的 还是什么时候是惯用的?你的标题是第一个;你的最后一句话是第二句话。 好点。我有点想知道两者,但我将其限制在一个问题上:什么时候真正需要显式递归?我将编辑问题。 高阶函数如mapfold 是通过递归实现的。如果您使用它们,从语义上讲,您使用的是递归。从语法上讲,您不必使用递归,除了一次 - 定义“规范递归函数” - fix f = let x = f x in x 但是fix 是在作弊——一个诚实的程序员会说使用fix 的函数是递归的。以下方案有丰富的理论fix;请参阅Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire 了解著名的治疗方法。 @user2407038 从 Haskellian 的角度来看,计算理论只能走到这一步——fix 的多态递归不太可能做得很好。 【参考方案1】:

我认为很多人发现递归数据定义比 Mu/Fix/Nu 类型更容易阅读。这不是绝对必要的,但在那里非常有用。

同样,您将使用递归为此类数据类型编写 Foldable/Unfoldabe 实例,但一旦提供这些实例,以后就不需要显式递归了。

【讨论】:

【参考方案2】:

假设我们有一种没有递归或类似的语言。这也意味着没有循环结构。这也意味着我们也有(非递归)类型,因此我们不能形成 Y 组合器并转义。在这种语言中,我们真的很弱,与我们的许多工具分开。

但是我们可以就这种语言提出一个非常好的问题。也就是说,为了让它变得像没有这些限制的语言一样强大,我们必须给予它最小的东西是什么?

原来有两个答案。

    我们可以引入递归绑定器,例如 let rec 命令或类似 Haskell 的 let 始终为 let rec。换句话说,一个允许我们定义let x = e in b 的结构,如果xe 中是空闲的,那么它被计算为方程x = e 上的一个不动点。

    我们可以引入函数fix :: (a -> a) -> a,使fix f一步减为f (fix f)

从上面的演示中应该很清楚,fix 可以使用递归绑定器来实现。不太清楚的是,递归绑定器可以使用修复从非递归绑定器实现,但我们在这里:

let x = fix $ \this -> e

this 指的是整个表达式,最终绑定为x,这正是我们想要的。


那我为什么要特意说出以上所有内容呢?

本质上,我想说的是,只要您愿意在该列表中考虑 fix,就不可能说递归一定是通过像 map 这样的 HOF 组合子实现的。我还想争辩说,由该集合中的组合器实现的任何递归都可以使用递归绑定器“显式地”完成。它们同样强大。

当您单独考虑像 foldr/unfoldr 这样的 HOF 组合子时,有趣的部分就出现了。这些在技术上比fix/recursive binders 稍微。优点是,如果您仅根据一组精选的 foldr/unfoldr 类似原则构建编程语言,那么您可以获得非常丰富的亚图灵完整语言,全部或保证终止。

【讨论】:

以上是关于什么时候需要显式递归?的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候需要在 Rust 中指定显式生命周期?

Java 啥时候需要显式类型参数?

为啥我不需要显式借出一个借来的可变变量?

二叉树的前序遍历之迭代

二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?

为啥我们需要显式调用 zero_grad()? [复制]