11
votes

"Introduction to Caml" says

Note, in Caml it is better to use Curried function definitions for multiple-argument functions, not tuples.

when comparing 'a -> 'b -> 'c calling conventions to 'a * 'b -> 'c.

When working with SML/NJ I got used to using tuple types for both input and output : ('a * 'b) -> ('c * 'd) so using tuples to express multiple inputs seems symmetric with the way I express multiple outputs.

Why is currying recommended for OCaml function declarations over tuple arguments? Is it just the greater flexibility that comes with allowing currying/partial evaluation, or is there some other benefit that derives from implementation details of the OCaml compiler?

3
The choice of currying most functions in Caml-light and subsequent versions is explained in the report "The ZINC experiment: an economical implementation of the ML language". One thing I remember is that with the proper evaluation scheme (described in the report), a curried function requires no allocation in order to be called. caml.inria.fr/pub/papers/xleroy-zinc.ps.gz - Pascal Cuoq
@PascalCuoq, whereas a tuple needs to be allocated, unpacked, and then GCed? - Mike Samuel
That's another question, but to answer, no, you shouldn't. Last time I checked it didn't even optimize the allocation of a, b in match a, b with x, y -> .... If you wish to check by yourself, I found that reading the x86 assembly generated by ocamlopt -S was convenient for me because I didn't have to learn a new representation. - Pascal Cuoq
Just to be clear, tupled functions don't usually require allocation either in compilers that optimise for them, as is the case in most implementations of SML. N-ary argument/results tuples are typically compiled to N arguments/results as part of standard flattening optimisations. Only when an argument/result tuple is given or used in a first-class manner additional boxing/unboxing takes place (i.e. exactly in those cases that you cannot express in curried form). - Andreas Rossberg
Pascal, your first comment is interesting, sourced, and an excellent completent to Andreas answer. Why did you not post it as an answer? I am quite sure that match a,b with x,y -> ... is optimized; in fact we even recently discussed an extension of this optimization, on which feedback would be welcome. - gasche

3 Answers

7
votes

I think a lot of it is convention -- standard library functions in OCaml are curried, whereas in Standard ML they are generally not except for some higher-order functions. However, there is one difference baked into the language: the operators (e.g. (*)) are curried in OCaml (e.g. int -> int -> int); whereas they are uncurried in Standard ML (e.g. op* can be (int * int) -> int). Because of that, built-in higher-order functions (e.g. fold) also take a function that is curried in OCaml and uncurried in Standard ML; that means for your function to work with that, you need to follow the respective convention, and it follows from there.

4
votes

Yes, it is mainly the notational convenience and the flexibility to do partial application. Curried functions are idiomatic in OCaml, and the compiler is likely to optimise them somewhat better than tupled functions (whereas SML compilers typically optimise for tuples).

The pros of tupling are the argument/result symmetry you mention (which is especially useful when composing functions) and perhaps the notational familiarity (at least for people coming from the non-functional world).

2
votes

Some comment about the optimisation in OCaml.

In OCaml, i noticed that tuples are always allocated when passing them as argument. Even if allocating in the primary heap is fast in ocaml, it is of course longer than doing nothing. Thus each time you pass a tuple as argument, there is some time spent to the allocation and filling of the tuple.

I expected that the ocaml compiler would be optimizing cases where it isn't needed to build the tuple. For instance, when you inline the called function, you may only use the tuple components and not the tuple itself. Thus the tuple could just be ignored. Unfortunately in this case OCaml doesn't remove the useless tuple and still perform the allocation. For this reason, it may not be a good idea to use tuples in critical sections of code.