Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will OCaml convert multi-argument function to currying or the other way around?

Tags:

ocaml

When I was learning OCaml essentials, I was told that every function in OCaml is actually a function with only one parameter. A multi-argument function is actually a function that takes one argument and returns a function that takes the next argumetn and returns ....

This is currying, I got that.

So my question is:

case 1

if I do

let plus x y = x + y

Inside OCaml when it compiles, will OCaml change it to let plus = fun x -> fun y -> x + y?


or the other way around that

case 2

If I do

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

OCaml will convert it to let plus x y = x + y?


Which case is true? What's the benifit or optimisation OCaml compiler has done in the correct case?

In addition, if case 2 is true, then what is the point to consider OCaml is doing currying? I mean it actually does the opposite way, right?

This question is actually related to Understand Core's `Fn.const`

like image 429
Jackson Tale Avatar asked Dec 23 '14 16:12

Jackson Tale


People also ask

What is the purpose of currying functional programming?

Currying is helpful when you have to frequently call a function with a fixed argument. Considering, for example, the following function: If we want to define the function error , warn , and info , for every type, we have two options. Currying provides a shorter, concise, and more readable solution.

What is currying in SML?

[Currying] Named after mathematician/logician Haskell Brooks Curry, currying is the act of transforming a function that takes multiple arguments into a function that returns a function that takes the remaining arguments.

What does fun do in OCaml?

Similarly, OCaml functions do not have to have names; they may be anonymous. For example, here is an anonymous function that increments its input: fun x -> x + 1 . Here, fun is a keyword indicating an anonymous function, x is the argument, and -> separates the argument from the body.

How do you return a function in OCaml?

OCaml doesn't have a return keyword — the last expression in a function becomes the result of the function automatically.


1 Answers

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.

like image 152
ivg Avatar answered Nov 15 '22 10:11

ivg