【问题标题】:Scope/order of evaluation of nested `let .. in ..` in OCamlOCaml中嵌套`let .. in ..`的评估范围/顺序
【发布时间】:2012-04-28 06:54:21
【问题描述】:

我这里有一些我不是 100% 理解的小问题:

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

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

【问题讨论】:

  • 仅供参考,此代码中不涉及闭包。这只是关于变量范围。
  • 由于这是一个纯表达式,因此评估顺序没有可见的影响。然而,由于 OCaml 确实具有命令式特性,因此指定了 let v = e1 in e2 的计算顺序:首先计算表达式 e1,然后计算 e2。正如 Chuck 所说,您的示例似乎比实际更难,因为您多次使用相同的名称。除了易于理解之外,这不会影响其他任何事情。你总是可以使用 3 个不同的变量名。
  • let x = 1 in let y = x+2 in let z = y+3 in z
  • (是的,完全正确!我们甚至使用了相同的变量名。)

标签: functional-programming ocaml evaluation let


【解决方案1】:

想象一下括号:

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】:

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

    正如 Chuck 所指出的,这个表达式中没有闭包。唯一的复杂性是由于范围规则。 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
    

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

    【讨论】:

      【解决方案3】:

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

      但是,在您的示例中,有一个令人困惑的部分:您在每个 let 构造中都使用了 same 变量名称 x。这很令人困惑,因为您会看到类似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 在让 y=x+2 中”而不是“让 x=1 在让 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”被限制在闭包,因此“x”在表达式“aaa”中不可见。由于这个原因,如果“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
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多