For domain modeling, I would recommend using types with named elements ; i.e. records, discriminatory union, and possibly a random class or interface.
Structurally, records and tuples are similar; in an expression of the type of algebraic data, they are types of products .
The difference is that with tuples the order of values matters, and the role of each element is implicit.
> (2016, 1, 2) = (2016, 1, 2);; val it : bool = true > (2016, 1, 2) = (2016, 2, 1);; val it : bool = false
In the example above, you can guess that these tuples model dates, but which ones? Is this the second of January 2016? Or is it the first of February 2016?
With records, on the other hand, the order of the elements does not matter, because you bind them and access them by name:
> type Date = { Year : int; Month : int; Day : int };; type Date = {Year: int; Month: int; Day: int;} > { Year = 2016; Month = 1; Day = 2 } = { Year = 2016; Day = 2; Month = 1 };; val it : bool = true
It is also clearer when you want to pull out the constituent values. You can easily get a year from a record value:
> let d = { Year = 2016; Month = 1; Day = 2 };; val d : Date = {Year = 2016; Month = 1; Day = 2;} > d.Year;; val it : int = 2016
It is more difficult to derive values from a tuple:
> let d = (2016, 1, 2);; val d : int * int * int = (2016, 1, 2) > let (y, _, _) = d;; val y : int = 2016
Of course, for pairs, you can use the built-in fst and snd functions to access elements, but for tuples with three or more elements, you cannot easily get values if you are not matching a pattern.
Even if you define custom functions, the role of each element is still implicitly determined by its serial number. It is easy to get the wrong order of values if they are of the same type.
So, for domain modeling, I always prefer explicit types so that it is clear what is happening.
Correspondents do not match, then?
Tuples are useful in other contexts. If you need to use ad hoc type to compile functions, they are more suitable than writing.
Consider, for example, Seq.zip , which allows you to combine two sequences:
let alphalues = Seq.zip ['A'..'Z'] (Seq.initInfinite ((+) 1)) |> Map.ofSeq;; val alphalues : Map<char,int> = map [('A', 1); ('B', 2); ('C', 3); ('D', 4); ('E', 5); ('F', 6); ('G', 7); ('H', 8); ('I', 9); ...] > alphalues |> Map.find 'B';; val it : int = 2
As you can see in this example, tuples are just a step towards the actual goal, which is a map of alphabetic values. It would be inconvenient if we had to determine the type of record when we wanted to put values together inside an expression. Tuples are well suited for this task.