OCaml中嵌套`let .. in ..`的评估范围/顺序

时间:2012-04-16 22:35:41

标签: functional-programming ocaml evaluation let

我在这里遇到一些问题,我不是百分之百理解:

let x = 1 in let x = x+2 in let x = x+3 in x

我知道这个表达式的结果是6,但只是想确定计算这个表达式的顺序;首先计算哪部分?

3 个答案:

答案 0 :(得分:8)

您在表达式let x=1 in let x=x+2 in ...中询问了评估的顺序。订单是“从左到右”!当您拥有let a=b in let c=d in ...链时,评估顺序始终是从左到右。

但是,在您的示例中有一个令人困惑的部分:您在每个x构造中使用了相同的变量名称let。这很令人困惑,因为您会看到let x=x+1之类的内容,看起来您正在“重新定义”x或“更改x的值”。但是在OCAML中实际上没有“改变”“x”!正如上面已经指出的那样,这里发生的是每次引入新变量,所以你的例子完全等同于

 let x = 1 in let y = x+2 in let z = y+3 in z;;

请注意,此处的评估顺序也是从左到右。 (在let构造的每个链中,它始终是从左到右。)在原始问题中,您选择将所有这些新变量称为“x”而不是xyz。这让大多数人感到困惑。最好避免这种编码风格。

但是我们如何检查我们是否正确地重命名了变量?为什么“让x = 1 in let y = x + 2”而不是“让x = 1 in let x = y + 2”?这个x=x+2业务非常令人困惑!那么,还有另一种理解let x=aaa in bbb评估的方法。构造

  let x=aaa in bbb

总是可以替换为应用于aaa

的以下闭包
  (fun x -> bbb) aaa

一旦你以这种方式重写它,你可以很容易地看到两件事:首先,OCAML不会在闭包内评估“bbb”,直到评估“aaa”。 (因此,let x=aaa in bbb的评估通过先评估aaa然后bbb,即“从左到右”进行评估。)其次,变量“x”是限制在闭包的主体上,因此在表达“aaa”中不能看到“x”。因此,如果“aaa”包含一个名为“x”的变量,那么它之前必须已经定义了一些值,并且它与闭包内的“x”无关。为清楚起见,最好用不同的名称调用此变量。

在你的例子中:

 let x=1 in let x=x+2 in let x=x+3 in x

被重写为

 (fun x -> let x=x+2 in let x=x+3 in x) 1

然后内部let构造也被重写:

 (fun x -> (fun x -> let x=x+3 in x) x+2 ) 1
 (fun x -> (fun x -> (fun x-> x) x+3) x+2 ) 1

现在让我们重命名每个函数内部函数的参数,我们总是可以在不改变代码含义的情况下进行:

 (fun x -> (fun y -> (fun z -> z) y+3) x+2 ) 1

这与

相同
 let x=1 in let y=x+2 in let z=y+3 in z

通过这种方式,您可以验证是否已正确重命名变量。

答案 1 :(得分:4)

想象一下parens:

let x = 1 in (let x = (x+2) in (let x = (x+3) in x))

然后替换(x = 1)其中x未被x的另一个声明覆盖并消除最外层的let

let x = (1+2) in (let x = (x+3) in x)

评估:

let x = 3 in (let x = (x+3) in x)

替换:

let x = (3+3) in x

评估:

let x = 6 in x

替换:

6

答案 2 :(得分:4)

(评论有点长,所以这里有一个小小的额外答案。)

正如查克指出的那样,这个表达没有关闭。唯一的复杂性是由于范围规则。 OCaml范围规则是通常的规则,即名称指的是最近的(最里面的)定义。在表达式中:

let v = e1 in e2

变量ve1中不可见(即无法命名)。如果(偶然)该名称的变量出现在e1中,则它必须引用(不同的)v的某个外部定义。但新的v可以(当然)在e2中命名。所以你的表达式等同于以下内容:

let x = 1 in let y = x+2 in let z = y+3 in z

在我看来,这更清楚,但它具有完全相同的含义。