When should a tuple record be used?

I am trying to practice a Driven Design project in F # and came across the following question:

Why should I use notation when using a tuple seems to require less syntax and seems more powerful in terms of pattern matching and just common usage scenarios?

For example, it seems to me that I no longer need to create a record type just to implement a discriminatory union, if I should use a tuple instead.

type Name = { First:string Middle:string option Last:string } type Duration = { Hours:int Minutes:int Seconds:int } type Module = | Author of Name | Title of string | Duration of Duration let tryCreateName (first, middle, last) = { First=first; Middle=Some middle; Last=last } let tryCreateDuration (hours, minutes, seconds) = { Hours=hours; Minutes=minutes;Seconds=seconds } let name = tryCreateName ("Scott", "K", "Nimrod") let hours = 1 let minutes = 30 let seconds = 15 let duration = tryCreateDuration (hours, minutes, seconds) 

Are my thoughts accurate?

Are tuples desirable compared to entries in most scenarios?

+6
source share
2 answers

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.

+12
source

You may be interested in additional parameters that are allowed only for types. To avoid changing the order of your arguments, I also added an optional name (maybe it also better models your problem area, for example, when Mononyms is encountered).

To represent the difference between two points in time, the structure already has a library function, System.TimeSpan .

Thus, your example can be written as

 type Name = { FirstName : string MiddleName : string option LastName : string option } with static member Create(firstName, ?middleName, ?lastName) = { FirstName = firstName MiddleName = middleName LastName = lastName } type System.TimeSpan with static member CreateDuration(hours, ?minutes, ?seconds) = System.TimeSpan( hours, defaultArg minutes 0, defaultArg seconds 0 ) let name = Name.Create("Scott", "K", "Nimrod") let duration = System.TimeSpan.CreateDuration(1, 30, 15) 

The idea is to provide extension methods that take alternating arguments of varying lengths and return a complex data structure, for example. record or structure.

+1
source

All Articles