在Haskell中,将尽可能多的代码写入高阶函数(如折叠,贴图和展开)是不恰当的。那么哪些代码不能用这些高阶函数编写?何时需要显式递归?
答案 0 :(得分:16)
假设我们有一种没有递归的语言或类似的东西。这意味着没有循环结构。这也意味着我们也有(非递归)类型,这样我们就不能形成Y-combinator并逃脱。在这种语言中,我们真的很弱,与我们的许多工具分开。
但我们可以就这种语言提出一个非常好的问题。也就是说,为了使它变得像没有这种限制的语言一样强大,我们必须给它最小的东西是什么?
事实证明有两个答案。
我们可以引入递归绑定器,例如let rec
命令或类似Haskell的let
,它总是let rec
。换句话说,这个结构允许我们定义let x = e in b
,以便x
中的e
是免费的,然后将其计算为等式x = e
上的固定点。
我们可以引入功能fix :: (a -> a) -> a
,以便fix f
一步减少到f (fix f)
。
从上面的演示中可以清楚地看出,fix
可以使用递归绑定器来实现。什么不太清楚的是,递归绑定器可以使用修复从非递归绑定器实现,但我们在这里:
let x = fix $ \this -> e
值this
指的是整个表达式,最终绑定为x
,这正是我们想要的。
那么为什么我会不顾一切地说出上述所有内容呢?
基本上,我想说,只要你愿意考虑{map
,就不可能说递归必然是通过HOF组合器实现的fix
。{{}} 1}}在该列表上。我还想争辩说,该集合中的组合器实现的任何递归都可以明确地完成"使用递归绑定器。他们同样强大。
当你自己考虑像foldr
/ unfoldr
这样的HOF组合时,有趣的部分会出现。这些在技术上有点弱而不是fix
/递归绑定器。优点是,如果您仅仅选择一组foldr
/ unfoldr
- 类似原则的编程语言,那么您可以获得一个非常丰富的子图灵完整语言,该语言可以总计或保证终止。
答案 1 :(得分:1)
我认为很多人发现递归数据定义比Mu / Fix / Nu类型更容易阅读。这不是绝对必要的,但在那里非常有用。
类似地,您将使用递归为这样的数据类型编写可折叠/展开实例,但是一旦提供了这些实例,就不需要显式递归了。