OCaml: Extract nth element of a tuple?

TL;DR; Stop trying to access directly the n-th element of a t-uple and use a record or an array as they allow random access.

You can grab the n-th element by unpacking the t-uple with value deconstruction, either by a let construct, a match construct or a function definition:

let ivuple = (5, 2, 1, 1)

let squared_sum_let =
  let (a,b,c,d) = ivuple in
  a*a + b*b + c*c + d*d

let squared_sum_match =
  match ivuple with (a,b,c,d) -> a*a + b*b + c*c + d*d

let squared_sum_fun (a,b,c,d) =
  a*a + b*b + c*c + d*d

The match-construct has here no virtue over the let-construct, it is just included for the sake of completeness.

Do not use t-uples, Don¹

There is only a few cases where using t-uples to represent a type is the right thing to do. Most of the times, we pick a t-uple because we are too lazy to define a type and we should interpret the problem of accessing the n-th field of a t-uple or iterating over the fields of a t-uple as a serious signal that it is time to switch to a proper type.

There is two natural replacements to t-uples: records and arrays.

When to use records

We can see a record as a t-uple whose entries are labelled, as such, they are definitely the most natural replacement to t-uples if we want to access them directly.

type ivuple = {
  a: int;
  b: int;
  c: int;
  d: int;
}

We then access directly the field a of a value x of type ivuple by writing x.a. Note that records are easily copied with modifications, as in let y = { x with d = 0 }. There is no natural way to iterate over the fields of a record, mostly because a record do not need to be homogeneous.

When to use arrays

A large² homogeneous collection of values is adequately represented by an array, which allows direct access, iterating and folding. A possible inconvenient is that the size of an array is not part of its type, but for arrays of fixed size, this is easily circumvented by introducing a private type — or even an abstract type. I described an example of this technique in my answer to the question “OCaml compiler check for vector lengths”.

Note on float boxing

When using floats in t-uples, in records containing only floats and in arrays, these are unboxed. We should therefore not notice any performance modification when changing from one type to the other in our numeric computations.


¹ See the TeXbook. ² Large starts near 4.


Since the length of OCaml tuples is part of the type and hence known (and fixed) at compile time, you get the n-th item by straightforward pattern matching on the tuple. For the same reason, the problem of extracting the n-th element of an "arbitrary-length tuple" cannot occur in practice - such a "tuple" cannot be expressed in OCaml's type system.

You might still not want to write out a pattern every time you need to project a tuple, and nothing prevents you from generating the functions get_1_1...get_i_j... that extract the i-th element from a j-tuple for any possible combination of i and j occuring in your code, e.g.

let get_1_1 (a) = a
let get_1_2 (a,_) = a
let get_2_2 (_,a) = a
let get_1_3 (a,_,_) = a
let get_2_3 (_,a,_) = a
...

Not necessarily pretty, but possible.

Note: Previously I had claimed that OCaml tuples can have at most length 255 and you can simply generate all possible tuple projections once and for all. As @Virgile pointed out in the comments, this is incorrect - tuples can be huge. This means that it is impractical to generate all possible tuple projection functions upfront, hence the restriction "occurring in your code" above.


It's not possible to write such a function in full generality in OCaml. One way to see this is to think about what type the function would have. There are two problems. First, each size of tuple is a different type. So you can't write a function that accesses elements of tuples of different sizes. The second problem is that different elements of a tuple can have different types. Lists don't have either of these problems, which is why you can have List.nth.

If you're willing to work with a fixed size tuple whose elements are all the same type, you can write a function as shown by @user2361830.

Update

If you really have collections of values of the same type that you want to access by index, you should probably be using an array.