为什么ocaml需要“let”和“let rec”?

时间:2012-02-17 09:38:25

标签: recursion functional-programming ocaml

  

可能重复:
  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会区分letlet rec?这是一个性能问题,还是更微妙的东西?

1 个答案:

答案 0 :(得分:33)

好吧,两者都可用而不是只有一个可以让程序员更加严格地控制范围。使用let x = e1 in e2时,绑定仅存在于e2的环境中,而let rec x = e1 in e2时绑定存在于e1e2的环境中。

(编辑:我想强调,是一个性能问题,根本没有任何区别。)

以下是两种情况:使用这种非递归绑定很有用:

  • 通过使用旧绑定的细化来遮蔽现有定义。类似于: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绑定递归(而不是非递归)肯定是有意义的,因为它与默认的惰性求值一致,这使得构建递归值非常容易 - 同时严格 - 默认语言对递归定义有意义有更多限制。