问题
I am trying to get the basics of F# clear before moving on to complex examples. The material I'm learning has introduced both Discriminate Unions and Record types. I have reviewed the material for both, but it is still unclear to me why we would use one over the other.
Most of the toy examples I have created seem to be implementable in both. Records seem to be very close to what I think of as an object in C#, but I am trying to avoid relying on mapping to c# as a way to understand F#
So...
Are there clear reason to use one over the other?
Are there certain canonical cases where one applies?
Are there certain functionalities available in one, but not the other?
回答1:
Think of it as a Record is 'and', while a discriminated union is 'or'. This is a string and an int:
type MyRecord = { myString: string
myInt: int }
while this is a value that is either a string or an int, but not both:
type MyUnion = | Int of int
| Str of string
This fictitious game can be in the Title screen, In-game, or displaying the final score, but only one of those options.
type Game =
| Title
| Ingame of Player * Score * Turn
| Endgame of Score
回答2:
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.
回答3:
If you come from C#, you can understand records as sealed classes with added values:
- Immutable by default
- Structural equality by default
- Easy to pattern match
- etc.
Discriminated unions encode alternatives e.g.
type Expr =
| Num of int
| Var of int
| Add of Expr * Expr
| Sub of Expr * Expr
The DU above is read as follows: an expression is either an integer, or a variable, or an addition of two expressions or subtraction between two expressions. These cases can't happen simultaneously.
You need all fields to construct a record. You can also use DUs inside records and vice versa
type Name =
{ FirstName : string;
MiddleName : string option;
LastName : string }
The example above shows that middle name is optional.
In F#, you often start modeling data with tuples or records. When advanced functionalities are required, you can move them to classes.
On the other hand, discriminated unions are used to model alternatives and mutual exclusive relationship between cases.
回答4:
One (slightly flawed) way to understand a DU is to look at it as a fancy C# "union", while a record is more like an ordinary object (with multiple independent fields).
Another way to look at a DU is to look at a DU as a two-level class hierarchy, where the top DU type is an abstract base class and the cases of the DU are subclasses. This view is actually close to the actual .NET implementation, although this detail is hidden by the compiler.
来源:https://stackoverflow.com/questions/17291932/when-to-use-a-discriminate-union-vs-record-type-in-f