12

"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?

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • 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. http://caml.inria.fr/pub/papers/xleroy-zinc.ps.gz – Pascal Cuoq May 19 '12 at 16:43
  • @PascalCuoq, whereas a tuple needs to be allocated, unpacked, and then GCed? – Mike Samuel May 19 '12 at 16:45
  • Yes, if the function is also intended to be called with pre-existing tuples (`f t`), there is no way around the fact that a short-lived temporary tuple `(x, y)` must be allocated to apply `f` to when what one has is only `x` and `y`. – Pascal Cuoq May 19 '12 at 16:48
  • @PascalCuoq, ok. So I shouldn't assume that `ocamlc` does escape-analysis to stack-allocate temporary tuples and the like. – Mike Samuel May 19 '12 at 16:53
  • 1
    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 May 19 '12 at 16:56
  • @PascalCuoq, Good to know. Thanks for the tip about `-S`. – Mike Samuel May 19 '12 at 17:46
  • 3
    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 May 19 '12 at 19:07
  • 1
    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](http://caml.inria.fr/mantis/view.php?id=4800) of this optimization, on which feedback would be welcome. – gasche May 19 '12 at 19:46
  • @gasche Indeed, `match a, b with` is optimized. I may have been thinking of `let f a b = let x, y = a, b in a + b + x + y ;;` which is not optimized in 3.11.2. I will keep in mind the extension you suggest but, for the same reason that I am running 3.11.2, I cannot conveniently try it in my current environment. – Pascal Cuoq May 19 '12 at 21:57

3 Answers3

8

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.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • 1
    Good point. There is another instance where the choice is baked in: datatype constructors. In SML they are tupled, in Haskell they are curried. Strangely enough, in OCaml they are tupled, which is a bit of a mismatch with the rest of the language. – Andreas Rossberg May 20 '12 at 11:33
  • 1
    The deeper meaning behind datatype constructors being tupled is that a tuple is an anonymous special-case data constructor. – Victor Nicollet Jun 05 '12 at 08:12
5

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).

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72
3

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.

Valentin Perrelle
  • 1,303
  • 1
  • 10
  • 17
  • I think this is no longer true. ["OCaml is smarter than I thought"](https://blogs.janestreet.com/ocaml-is-smarter-than-i-thought/) says "I'd prevented the inlining, but there was still no allocation! Why? Well, it turns out that OCaml can optimize a tuple-taking function to get the elements of the tuple passed in via registers, which is exactly what happened. And again, the compiler realized that no allocation was required." – Mike Samuel Jul 13 '15 at 13:38