Use records (called product types in functional programming theory) for complex data which is described by several properties, like a database record or some model entity:
type User = { Username : string; IsActive : bool }
type Body = {
Position : Vector2<double<m>>
Mass : double<kg>
Velocity : Vector2<double<m/s>>
}
Use discriminated unions (called sum types) for data possible values for which can be enumerated. For example:
type NatNumber =
| One
| Two
| Three
...
type UserStatus =
| Inactive
| Active
| Disabled
type OperationResult<'T> =
| Success of 'T
| Failure of string
Note that possible values for a discriminated union value are also mutually exclusive -- a result for an operation can be either Success
or a Failure
, but not both at the same time.
You could use a record type to encode a result of an operation, like this:
type OperationResult<'T> = {
HasSucceeded : bool
ResultValue : 'T
ErrorMessage : string
}
But in case of operation failure, it's ResultValue
doesn't make sense. So, pattern matching on a discriminated union version of this type would look like this:
match result with
| Success resultValue -> ...
| Failure errorMessage -> ...
And if you pattern match the record type version of our operation type it would make less sense:
match result with
| { HasSucceeded = true; ResultValue = resultValue; ErrorMessage = _ } -> ...
| { HasSucceeded = false; ErrorMessage = errorMessage; ResultValue = _ } -> ...
It looks verbose and clumsy, and is probably less efficient as well. I think when you get a feeling like this it's probably a hint that you're using a wrong tool for the task.