Both let plus x y = x + y
and let plus = fun x -> fun y -> x + y
will be compiled to the same code:
camlPlus__plus:
leaq -1(%rax, %rbx), %rax
ret
Yes, exactly two assembler instructions, without any prologues and epilogues.
OCaml compiler performs several steps of optimizations, and actually "thinks" in a different categories. For example, both functions are represented with the same lambda code:
(function x y (+ x y))
I think, that according to the lambda above, you may think that OCaml compiler transforms to a non-curried version.
Update
I would also like to add a few words about the core's const
function. Suppose we have two semantically equivalent representations of the const function:
let const_xxx c = (); fun _ -> c
let const_yyy c _ = c
in a lambda form they will be represented as:
(function c (seq 0a (function param c))) ; const_xxx
(function c param c) ; const_yyy
So, as you can see, const_xxx
is indeed compiled in a curried form.
But the most interesting question, is why it is worth to write it in a such obscure code. Maybe there're some clues in assembly output (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
What about const_yyy
? It is compiled simply as:
camlPlus__const_yyy_1010:
ret
Just return the argument. So, it is assumed that the actual point of optimization, is that in const_xxx
the closure creation is compiled inside the function and should be fast. On the other hand, const_yyy
doesn't expect to be called in a curried way, so if you will call it without all the needed parameters, then compiler needs to add the code that creates a closure in the point of const_yyy
partial application (i.e., to perform all the operations in the const_xxx
every time you call const_xxx x
).
To conclude, const
optimization creates a function that is optimized for partial application. Although, it comes with cost. A non-optimized const
function will outperform the optimized if they are called with all parameters. (Actually my parameter even droped a call to const_yyy
when I applied it with two args.
let plus x y = x + y
IS syntactic sugar forlet plus = fun x -> fun y -> x + y
. The second is how the first is defined in the language. There is no "change" because they are the same. As Jeffrey Scofield's answer says, a "non-curried" version would belet plus (x, y) = x + y
– user102008ocamlc
(which also happen to work with ocaml!), which makes the compiler dumps representations of the code at different stages of compilations. Quite interesting to compare the way code is being translated throughout the compilation process, I wanted to mention them here (-dparsetree
etc.), but it seems it's been mentioned in one of the answers below. – didierc