`let rec`绑定与OCaml中常规`let`绑定的优点

Ram*_*nir 5 ocaml

对于这个问题,这个问题是一个后续问题:如何在OCaml中进行协变可观察

接受的答案的作者(随便)指出,使用let rec绑定两个独立的值比两个单独的let绑定更"经济" .

let make x =
  let queue = Queue.create () in
  let obj = x in
  let watch f = Queue.add f queue in
  let notify () = Queue.iter (fun f -> f x) queue in
  { obj; watch; notify; }
Run Code Online (Sandbox Code Playgroud)

VS

let make x =
  let queue = Queue.create () in
  let obj = x in
  let rec watch f = Queue.add f queue
  and notify () = Queue.iter (fun f -> f x) queue in
  { obj; watch; notify; }
Run Code Online (Sandbox Code Playgroud)

他的陈述是否正确?如果是这样,为什么第二个版本更"经济"?

phi*_*mue 5

正如我在评论中所说,似乎通过使用let rec,您可以避免创建更多的闭包.为了检查这一点,我创建了两个略有不同的文件:

test1.ml有"通常"的方式没有let rec:

let test1 x =
    let x = 5 in
    let w () = x + 1 in
    let n () = x + 1 in
    w () + n ()
Run Code Online (Sandbox Code Playgroud)

另一方面,test2.ml用途let rec:

let test2 x =
    let x = 5 in
    let rec w () = x + 1 
    and n () = x + 1 in
    w () + n ()
Run Code Online (Sandbox Code Playgroud)

然后我将ocamlc -dinstr两个文件(即我为两个文件生成字节码)并获得以下内容:

因为test1.ml,我得到了:

    branch L2
L3: envacc 1
    offsetint 1
    return 1
L4: envacc 1
    offsetint 1
    return 1
L1: const 5
    push
    acc 0
    closure L4, 1
    push
    acc 1
    closure L3, 1
    push
    const 0a
    push
    acc 1
    apply 1
    push
    const 0a
    push
    acc 3
    apply 1
    addint
    return 4
L2: closure L1, 0
    push
    acc 0
    makeblock 1, 0
    pop 1
    setglobal Closuretest!
Run Code Online (Sandbox Code Playgroud)

该文件test2.ml导致以下结果:

    branch L2
L3: envacc 3
    offsetint 1
    return 1
L4: envacc 1
    offsetint 1
    return 1
L1: const 5
    push
    acc 0
    closurerec 3 4, 1
    const 0a
    push
    acc 1
    apply 1
    push
    const 0a
    push
    acc 3
    apply 1
    addint
    return 4
L2: closure L1, 0
    push
    acc 0
    makeblock 1, 0
    pop 1
    setglobal Closuretest2!
Run Code Online (Sandbox Code Playgroud)

从一个Caml虚拟机 - 指令集文件(不幸的是,我不知道它是否是官方的,但看起来合理),似乎是指令closureclosurerec在堆栈上生成一个闭包.如您所见,字节码test1.ml总共生成3个闭包,而test2.ml只生成两个闭包(一个过孔closurerec).

我不是汇编大师,但你可以测试ocamlopt -S filename.ml,以便编译器离开(并且不删除)程序集(然后进入filename.s),在那里你也可以发现类似的差异.

  • OCaml编译器具有在管道的各个阶段打印中间表示的选项.`-dlambda`(在打字和模式匹配编译之后)和`-dcmm`(在关闭消除和内联之后)是最有用的 - 并且比`-S`的输出更具可读性. (3认同)