在有关ocaml的书中,我看到了let (-) x y = y - x in 1 - 2 - 3
和let rec (-) x y = y - x in 1 - 2 - 3
这两个例子。当我看到前一个功能时,我以为我理解了后面的技巧,直到看到了后者的功能。似乎后一个函数有一些堆栈溢出问题,但是为什么会这样呢? ocaml如何分别评估这两个表达式?
答案 0 :(得分:1)
重命名let
绑定函数可能会有所帮助。
let (-) x y = y - x
in 1 - 2 - 3
(* The above expression is equivalent to the following. *)
let f x y = y - x
in f (f 1 2) 3
(* Which reduces to the following. *)
let f x y = y - x
in 3 - (2 - 1)
请注意,我们定义的功能let (-) x y
与我们在定义y - x
中使用的功能不同。这是因为没有let
的{{1}}不会在定义内绑定函数名称。因此,定义中的rec
运算符是本机减号运算符。因此,结果为(-)
。
现在,考虑第二个示例。
2
现在,let rec (-) x y = y - x
in 1 - 2 - 3
(* The above expression is equivalent to the following. *)
let rec f x y = f y x
in f (f 1 2) 3
减少到什么?它减少到f 1 2
,减少到f 2 1
,减少到f 1 2
,依此类推。现在,在没有尾调用优化的语言中,这将导致堆栈溢出错误。但是,在OCaml中,它应该永远运行而不返回。
答案 1 :(得分:0)
let表达式的语法为let <name> = <expr1> in <expr2>
,它定义<name>
绑定到<expr1>
中的<expr2>
。 <name>
本身在<expr1>
的范围内不可见,换句话说,默认情况下它不是递归的。并且该功能可以(经常使用)为相同的名称赋予新的含义,例如,这是规范的OCaml代码,
let example () =
let name = "Alice" in
let name = "Professor " ^ name in
print_endline name
let (-) x y = y - x in 1 - 2 - 3
中使用了相同的技术方法,其中我们根据原始(-)
运算符重新定义了(-)
,而该运算符在{{1 }}表达式。
但是,当我们在let定义中添加y - x
关键字时,该名称会在当前定义的表达式的范围内立即显示,例如
rec
let rec (-) x y = y - x
(* ^ | *)
(* | | *)
(* +----------+ *)
中的-
是指当前定义的函数,因此我们有一个递归定义,表示x减y为y减x-虚假定义。