OCaml会将多参数函数转换为currying还是反过来?

时间:2014-12-23 16:53:53

标签: ocaml

当我学习OCaml essentials时,我被告知OCaml中的每个函数实际上只是一个只有一个参数的函数。多参数函数实际上是一个函数,它接受一个参数并返回一个函数,该函数接受下一个参数并返回....

这是好事,我明白了。

所以我的问题是

案例1

如果我这样做

let plus x y = x + y

在OCaml编译时,OCaml会将其更改为let plus = fun x -> fun y -> x + y吗?


或其他方式

案例2

如果我这样做

let plus = fun x -> fun y -> x + y

OCaml会将其转换为let plus x y = x + y


哪种情况属实?好的或优化的OCaml编译器在正确的情况下做了什么?

此外,如果 case 2 为真,那么OCaml正在考虑做什么呢?我的意思是它实际上是相反的,对吗?

此问题实际上与Understand Core's `Fn.const`

有关

4 个答案:

答案 0 :(得分:7)

let plus x y = x + ylet plus = fun x -> fun y -> x + y都将编译为相同的代码:

camlPlus__plus:
    leaq    -1(%rax, %rbx), %rax
    ret

是的,正好是两个汇编指令,没有任何序言和结尾。

OCaml编译器执行几个优化步骤,实际上"认为"在不同的类别。例如,两个函数都用相同的lambda代码表示:

(function x y (+ x y))

我认为,根据上面的lambda,您可能会认为OCaml编译器转换为非curried版本。

更新

我还想补充一下关于核心const功能的几句话。假设我们有const函数的两个语义等价的表示:

let const_xxx c = (); fun _ -> c
let const_yyy c _ = c

以lambda形式表示为:

(function c (seq 0a (function param c))) ; const_xxx
(function c param c)                     ; const_yyy

因此,正如您所看到的,const_xxx确实以curry形式编译。

但最有趣的问题是,为什么值得在一个如此模糊的代码中编写它。也许在汇编输出中有一些线索(amd64):

camlPlus__const_xxx_1008:
    subq    $8, %rsp
.L101:
    movq    %rax, %rbx                    ; save c into %rbx (it was in %rax)
.L102:  
    subq    $32, %r15                     ; allocate memory for a closure
    movq    caml_young_limit(%rip), %rax  ; check
    cmpq    (%rax), %r15                  ; that we have memory, if not
    jb      .L103                         ; then free heap and go back
    leaq    8(%r15), %rax                 ; load closure address to %rax
    movq    $3319, -8(%rax)
    movq    camlPlus__fun_1027(%rip), %rdi
    movq    %rdi, (%rax)
    movq    $3, 8(%rax)
    movq    %rbx, 16(%rax)                ; store parameter c in the closure
    addq    $8, %rsp             
    ret                                   ; return the closure
.L103:  call    caml_call_gc@PLT
.L104:  jmp .L102

const_yyy怎么样?它编译为:

camlPlus__const_yyy_1010:
    ret

只需返回参数即可。因此,假设实际的优化点是在const_xxx中,闭包创建是在函数内编译的,应该很快。另一方面,const_yyy并不期望以curry方式调用,所以如果你在没有所有需要的参数的情况下调用它,那么编译器需要添加创建闭包的代码const_yyy部分应用程序(即每次调用const_xxx时执行const_xxx x中的所有操作)。

总而言之,const优化会创建一个针对部分应用程序进行优化的函数。虽然,它带来了成本。如果使用所有参数调用它们,则非优化的const函数将优于优化的函数。 (实际上,当我用两个args应用它时,我的参数甚至会调用const_yyy

答案 1 :(得分:5)

就OCaml语言的语义而言,这两个定义都是完全等同于curry函数的定义。在OCaml语言的语义中没有多参数函数。

然而,实施是另一回事。具体而言,OCaml语言的当前实现在其内部表示中支持多参数函数。当以某种方式定义curried函数(即let f x y = ...let f = fn x -> fn y -> ...)时,这将在内部编译为多参数函数。但是,如果它的定义不同(如链接问题中的let f x = (); fn y -> ...),它将被编译为curried函数。这只是一种优化,不会以任何方式影响语言的语义。定义curried函数的所有三种方法在语义上都是等价的。

关于什么变成什么的具体问题:由于转换不是从一个OCaml代码转换为另一个OCaml代码,而是从OCaml代码到内部表示,我认为最准确描述它的方式是说OCaml编译器在内部将let plus x y = x + ylet plus = fn x -> fn y -> x + y都转换成相同的东西,而不是将它变为另一个。

答案 2 :(得分:4)

案例1和案例2都是curry函数。这是非咖喱版:

let plus (x, y) = x + y

答案 3 :(得分:3)

好的,我了解到本机编译器将优化您的代码,我期望它做什么。但这是字节码编译器:

let plus1 x y = x + y
let plus2 = fun x y -> x + y
let plus3 = function x -> function y -> x + y
ocamlc -c -dinstr temp.ml处理的

给了我:

       branch L4
        restart
L1:     grab 1
        acc 1
        push
        acc 1
        addint
        return 2
        restart
L2:     grab 1
        acc 1
        push
        acc 1
        addint
        return 2
        restart
L3:     grab 1
        acc 1
        push
        acc 1
        addint
        return 2

表示结果完全相同,它只是语法差异。这些论点是一个接一个的。

顺便说一下,还有一个语法点:fun可以用n个参数编写,function只能用一个参数编写。

从概念的角度来看,我非常赞成function x -> function y ->而不是其他人。