为什么 F# 和 OCaml(可能还有其他语言)中的函数默认情况下不是递归的?
换句话说,为什么语言设计者认为在如下声明中显式地输入 rec
是个好主意:
let rec foo ... = ...
并且默认情况下不赋予函数递归能力?为什么需要显式的 rec
构造?
最佳答案
原始 ML 的法国和英国后裔做出了不同的选择,他们的选择几十年来一直被继承到现代变体。所以这只是遗留问题,但它确实影响了这些语言中的习语。
在法语 CAML 语言系列(包括 OCaml)中,函数默认不是递归的。这种选择使得在这些语言中使用 let
可以轻松地取代函数(和变量)定义,因为您可以在新定义的主体中引用先前的定义。 F# 从 OCaml 继承了此语法。
例如,在 OCaml 中计算序列的香农熵时取代函数 p
:
let shannon fold p =
let p x = p x *. log(p x) /. log 2.0 in
let p t x = t +. p x in
-. fold p 0.0
请注意高阶 shannon
函数的参数 p
如何被主体第一行中的另一个 p
取代,然后正文第二行中的另一个 p
。
相反,ML 语言家族的英国 SML 分支采取了另一种选择,并且 SML 的 fun
绑定(bind)函数默认是递归的。当大多数函数定义不需要访问其函数名称的先前绑定(bind)时,这会导致代码更简单。但是,被取代的函数使用不同的名称(f1
、f2
等),这会污染作用域,并可能意外调用函数的错误“版本” 。现在,隐式递归 fun
绑定(bind)函数和非递归 val
绑定(bind)函数之间存在差异。
Haskell 可以通过将定义限制为纯定义来推断定义之间的依赖关系。这使得玩具 sample 看起来更简单,但在其他方面却付出了巨大的代价。
请注意,Ganesh 和 Eddie 给出的答案是转移注意力的。他们解释了为什么不能将函数组放置在巨大的 let rec ... and ...
中,因为它会影响类型变量的泛化时间。这与 SML 中默认的 rec
无关,但 OCaml 中则不然。
关于f# - 为什么 OCaml/F# 中的函数默认不是递归的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/900585/