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

时间:2014-02-01 16:27:50

标签: ocaml

对于这个问题,这个问题是一个后续问题:How to make a covariant observable in 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; }

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; }

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

1 个答案:

答案 0 :(得分: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 ()

另一方面,test2.ml使用let rec

let test2 x =
    let x = 5 in
    let rec w () = x + 1 
    and n () = x + 1 in
    w () + n ()

我然后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!

文件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!

来自Caml Virtual Machine — Instruction set Document(不幸的是,我不知道它是否是正式的,但看起来很合理),似乎closureclosurerec指令生成了关闭堆栈。如您所见,test1.ml的字节码总共生成了3个闭包,而test2.ml只生成了两个闭包(一个来自closurerec)。

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