我在这里遇到一些问题,我不是百分之百理解:
let x = 1 in let x = x+2 in let x = x+3 in x
我知道这个表达式的结果是6,但只是想确定计算这个表达式的顺序;首先计算哪部分?
答案 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”而不是x
,y
和z
。这让大多数人感到困惑。最好避免这种编码风格。
但是我们如何检查我们是否正确地重命名了变量?为什么“让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
变量v
在e1
中不可见(即无法命名)。如果(偶然)该名称的变量出现在e1
中,则它必须引用(不同的)v
的某个外部定义。但新的v
可以(当然)在e2
中命名。所以你的表达式等同于以下内容:
let x = 1 in let y = x+2 in let z = y+3 in z
在我看来,这更清楚,但它具有完全相同的含义。