The arity of a closure is the exact number of arguments expected by the underlying code pointer (the second one if there are two of them). It is an implementation detail, and does not necessarily have any link to the source-level function, though obviously in many cases an OCaml function with n
parameters will be compiled to a single closure of arity n
.
Here are a few examples to illustrate:
(* Hack to get the arity on current versions of the compiler.
Only works in native mode. *)
let arity x = Obj.(obj (field (repr x) 1) asr (Sys.int_size - 8))
let f { contents = x } y = x + y (* Two parameters *)
let () = Printf.printf "Arity of f is %d%!\n" (arity f)
let id x = x
let () = Printf.printf "Arity of id is %d%!\n" (arity id)
let () = id id id id id () (* Applied to 5 arguments *)
let tupled (x, y) = x + y (* Single tupled argument *)
let () = Printf.printf "Arity of tupled is %d%!\n" (arity tupled)
Regarding the representation of sum_six 1 2 3 4 5
vs psum1 1 2 3 4 5
, the first one is the result of an optimisation: sum_six
is known to have arity more than 5, so we can build the partial application closure at compile time with all the parameters in a single closure.
However, in the case of psum1
, it has arity 1, and will return a function of arity 5 (the partial application of sum_six
to a single argument), so the compiler will generate an exact application of psum1
to the first argument 1
, but the remaining 4 arguments will be passed using a generic application of an unknown function; and since this unknown function will end up, at runtime, having an arity (5) different from the number of arguments, so they will be passed one by one using the partial application code pointers. This is what creates the nested closures.
Note that if you use Flambda, there is a good chance that the compiler will manage to generatethe same result for psum1 1 2 3 4 5
as for sum_six 1 2 3 4 5
. You may have to increase the optimisation level a bit for the optimisation to trigger fully.
What you should keep in mind when looking at arities, is that all closures with non-1 arity are actually optimisations over the basic representation. In particular, the bytecode compiler doesn’t optimise the representation of closures, always generating closures of arity 1 (and storing 0 in the arity field, which is never read in bytecode).