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

Jac*_*ale 5 ocaml

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

这是好事,我明白了.

所以我的问题是:

情况1

如果我做

let plus x y = x + y
Run Code Online (Sandbox Code Playgroud)

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


或者相反

案例2

如果我做

let plus = fun x -> fun y -> x + y
Run Code Online (Sandbox Code Playgroud)

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


哪种情况属实?在正确的情况下,OCaml编译器的优点或优化是什么?

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

这个问题实际上与理解Core的`Fn.const`有关

ivg*_*ivg 7

双方let plus x y = x + ylet plus = fun x -> fun y -> x + y会被编译成相同的代码:

camlPlus__plus:
    leaq    -1(%rax, %rbx), %rax
    ret
Run Code Online (Sandbox Code Playgroud)

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

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

(function x y (+ x y))
Run Code Online (Sandbox Code Playgroud)

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

更新

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

let const_xxx c = (); fun _ -> c
let const_yyy c _ = c
Run Code Online (Sandbox Code Playgroud)

在lambda形式中,它们将表示为:

(function c (seq 0a (function param c))) ; const_xxx
(function c param c)                     ; const_yyy
Run Code Online (Sandbox Code Playgroud)

因此,正如您所看到的,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
Run Code Online (Sandbox Code Playgroud)

怎么样const_yyy?它编译简单如下:

camlPlus__const_yyy_1010:
    ret
Run Code Online (Sandbox Code Playgroud)

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

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


sep*_*p2k 5

就OCaml语言的语义而言,这两个定义完全等同于curry函数的定义.在OCaml语言的语义中没有多参数函数这样的东西.

然而,实施是另一回事.具体而言,OCaml语言的当前实现在其内部表示中支持多参数函数.当curried函数以某种方式定义(即as 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进入内部同样的事情,而不是它变成一个到另一个.