F# makes it easy to model a domain with discriminated unions (= it is one of these values) and measures (type-safe numbers). Excerpt from our expenses sub-system:
* in a type definition stands for a tuple: int * string
is a tuple of an int and a string, like 42, "this is a tuple"
. The definition uses a *
, the values a ,
.
Single-case discriminated unions like type DimensionId = DimensionId of Guid
are a simple way of making basic types type-safe. There is an alternative: using FSharp.UMX allows using measures on GUIDs (I’ll post an example later).
Normal discriminated unions are great for expressing that a value is either this or that:
type MyDiscriminatedUnion =
| This
| That of MyData: string
And you can add data to the cases.
And, of course, all these types are immutable and support structural comparison out of the box.
We have tons tutorials about domain modelling with DUs which are pretty much copies of the Scott Wlaschin’s blog post. And as with the original blog post there’s a huge problem when using DUs and records for modelling: all fine and dandy until you have to access the actual value or update an entity – and then you drown in so much line noise that Java dies from envy.
Your experience doesn’t match my experience.
– Accessing values of a record is the same as in most other languages
– accessing a union case with a match expression isn’t what I would call boilerplate code
– updating values in a record with the
{ value with FOO = 42 }
syntax is very convenient and preserves immutability.– when one has to frequently update fields, then go with a mutable type and it’s the same as in all C-style languages