Visual Studio 2010 - Visual F#
Discriminated Unions (F#)

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types.

type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...]
   ...
Remarks

Discriminated unions are similar to union types in other languages, but there are differences. As with a union type in C++ and C# or a variant type in Visual Basic, the type of the value is not fixed; it can be one of several distinct options. Unlike unions in other languages, however, each of the possible options is given a case identifier. The case identifiers are names for the various possible types of values that objects of this type could be; the values are optional. If values are not present, the case is equivalent to an enumeration case. If values are present, each value can either be a single value of a specified type, or a tuple that aggregates multiple values of the same or different types.

The option type is a simple discriminated union in the F# core library. The option type is declared as follows.

// The option type is a discriminated union.
type Option =
   | Some of 'a
   | None

The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. The Some case has an associated value whose type is represented by the type parameter 'a. The None case has no associated value. Thus the option type specifies a generic type that either has a value of some type or no value.

The case identifiers can be used as constructors for the discriminated union type. For example, the following code is used to create values of the option type.

F#
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None

The case identifiers are also used in pattern matching expressions. In a pattern matching expression, identifiers are provided for the values associated with the individual cases. For example, in the following code, x is the identifier given the value that is associated with the Some case of the option type.

F#
let printValue opt =
    match opt with
    | Some x -> printfn "%A" x
    | None -> printfn "No value."
let printValue opt =
    match opt with
    | Some(x) -> printfn "%A" x
    | None -> printfn "No value."

In a constructor or in a pattern, the parentheses around the identifier (x in this case) are optional when there is only one value. When the union case is a tuple, parentheses are required.

You can often use a discriminated union as a simpler alternative to a small object hierarchy. For example, the following discriminated union could be used instead of a Shape base class that has derived types for circle, square, and so on.

F#
type Shape =
| Circle of double
| Ellipse of double * double
| EquilateralTriangle of double
| Triangle of double * double * double
| Square of double
| Rectangle of double * double
type Shape =
| Circle of double
| Ellipse of double * double
| EquilateralTriangle of double
| Triangle of double * double * double
| Square of double
| Rectangle of double * double

Instead of a virtual method to compute an area or perimeter, as you would use in an object-oriented implementation, you can use pattern matching to branch to appropriate formulas to compute these quantities. In the following example, different formulas are used to compute the area, depending on the shape. Heron of Alexandria's formula computes the area of a triangle from the lengths of the three sides.

F#
let pi = 3.141592654

let heronsFormula a b c =
    let p = (a + b + c)/2.0
    sqrt (p * (p - a) * (p - b) * (p - c))

let area myShape =
    match myShape with
    | Circle radius -> pi * radius * radius
    | Ellipse (a, b) -> pi * a * b
    | EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
    | Triangle (a, b, c) -> heronsFormula a b c
    | Square s -> s * s
    | Rectangle (h, w) -> h * w

let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square with side %f: %f" squareSide (area mySquare)

let a, b, c = 30.0, 40.0, 50.0
let myTriangle = Triangle(a, b, c)
printfn "Area of triangle with sides %f %f %f is %f" a b c (area myTriangle)

let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle with height %f and width %f is %f" height width (area myRectangle)
let pi = 3.141592654

let heronsFormula a b c =
    let p = (a + b + c)/2.0
    sqrt (p * (p - a) * (p - b) * (p - c))

let area myShape =
    match myShape with
    | Circle radius -> pi * radius * radius
    | Ellipse (a, b) -> pi * a * b
    | EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
    | Triangle (a, b, c) -> heronsFormula a b c
    | Square s -> s * s
    | Rectangle (h, w) -> h * w

let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)

let a, b, c = 30.0, 40.0, 50.0
let myTriangle = Triangle(a, b, c)
printfn "Area of triangle that has sides %f %f %f is %f" a b c (area myTriangle)

let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)

The output is as follows:

Area of square that has side 10.000000: 100.000000
Area of triangle that has sides 30.000000 40.000000 50.000000 is 600.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000
See Also

Other Resources

Page view tracker