可能重复:
Why are functions in Ocaml/F# not recursive by default?
OCaml使用let
来定义新函数,或使用let rec
来定义递归函数。为什么它需要这两者 - 我们不能只使用let
来处理所有事情吗?
例如,要在OCaml中定义非递归后继函数和递归阶乘(实际上,在OCaml解释器中),我可能会写
let succ n = n + 1;;
let rec fact n =
if n = 0 then 1 else n * fact (n-1);;
而在Haskell(GHCI)我可以写
let succ n = n + 1
let fact n =
if n == 0 then 1 else n * fact (n-1)
为什么OCaml会区分let
和let rec
?这是一个性能问题,还是更微妙的东西?
答案 0 :(得分:33)
好吧,两者都可用而不是只有一个可以让程序员更加严格地控制范围。使用let x = e1 in e2
时,绑定仅存在于e2
的环境中,而let rec x = e1 in e2
时绑定存在于e1
和e2
的环境中。
(编辑:我想强调,不是一个性能问题,根本没有任何区别。)
以下是两种情况:使用这种非递归绑定很有用:
通过使用旧绑定的细化来遮蔽现有定义。类似于:let f x = (let x = sanitize x in ...)
,其中sanitize
是一个确保输入具有某些所需属性的函数(例如,它采用可能非标准化向量的范数等)。这在某些情况下非常有用。
元编程,例如宏写作。想象一下,我想为任何表达式SQUARE(foo)
定义一个去掉let x = foo in x * x
的宏foo
。我需要这个绑定来避免输出中的代码重复(我不希望SQUARE(factorial n)
计算factorial n
两次)。如果let
绑定不是递归的,那么这只是卫生的,否则我无法写let x = 2 in SQUARE(x)
并获得正确的结果。
所以我声称确实提供递归和非递归绑定非常重要。现在,let-binding的默认行为是一个常规问题。您可以说let x = ...
是递归的,并且必须使用let nonrec x = ...
来获取非递归绑定器。选择一个默认值或另一个默认值是您想要支持哪种编程风格,并且有充分的理由做出任何一种选择。 Haskell因这种非递归模式不可用而受到损害,而OCaml在类型级别上具有完全相同的缺陷:type foo = ...
是递归的,并且没有非递归选项可用 - 请参阅this blog post。
¹:当谷歌代码搜索可用时,我用它在Haskell代码中搜索模式let x' = sanitize x in ...
。这是非递归绑定不可用的常用解决方法,但它不太安全,因为您以后可能会错误地编写x
而不是x'
- 在某些情况下,您希望两者都可用,所以选择一个不同的名字可以是自愿的。一个好的习惯用法是为第一个x
使用更长的变量名称,例如unsanitized_x
。无论如何,只是从字面上寻找x'
(没有其他变量名称)和x1
会产生很多结果。 Erlang(以及所有试图使变量阴影变得困难的语言:Coffeescript等)都有更糟糕的问题。
也就是说,默认情况下选择Haskell绑定递归(而不是非递归)肯定是有意义的,因为它与默认的惰性求值一致,这使得构建递归值非常容易 - 同时严格 - 默认语言对递归定义有意义有更多限制。