Exercise 6: Types and Discriminated Unions

Like most modern languages, F# provides facilities to create user defined types. F# also allows developers to create “discriminated unions”, which represent data or structures and convey different meanings depending on how they are used.

The following exercise will demonstrate how to create simple types in F#, create discriminated unions and then use those unions in pattern matching algorithms.

Task 1 – Creating a Simple Type in F#

In this task you will create a simple type in F#.

  1. You will create a type to hold an item name and a unit price. You will define the item name and unit cost as type aliases first. In the F# interactive console, enter the following command:

    F#

    type ItemName = string;;

    Response

    type ItemName = string

    F#

    type UnitPrice = float;;

    Response

    type UnitPrice = float

  2. Record Types in F# are akin to classes or structures in that they are types that are composites of different values. You can use the standard primitive F# types in your record types, or your user defined types. In the F# interactive console, enter the following command:

    F#

    type OrderItem = {ItemOrdered : ItemName; Quantity : int; PricePer : UnitPrice};;

    Response

    type OrderItem = {ItemOrdered: ItemName; Quantity: int; PricePer: UnitPrice;}

    Note:
    The syntax for creating the record type is pretty simple. The fields are enclosed in curly braces with each field being delineated by a semi-colon. The fields themselves are composed of a name and a type separated by a colon. You can see that the ItemOrdered and PricePer fields are made up of type aliases you defined, but you are also able to define the Quantity field as a normal F# type.

Task 2 – Creating a Discriminated Union

In this task you will see how discriminated unions can be used to hold temperature information. While Celsius, Fahrenheit and Kelvin temperatures are quite different, a discriminated union is able to represent the correct value. Discriminated unions are made up of two pieces of information, the type of the value, known in F# as the constructor, and the value itself.

  1. Create a type which is a discriminated union to hold your temperature data. In the F# interactive console, enter the following command:

    F#

    type temperature = | Celsius of float | Fahrenheit of float | Kelvin of float;;

    Response

    type temperature = | Celsius of float | Fahrenheit of float | Kelvin of float;;

    Note:
    In this case, the constructors are Celsius, Fahrenheit and Kelvin which all take a value of type float.

  2. To use the type you simply declare an identifier and tell F# which type of value you are providing. In the F# interactive console, enter the following command:

    F#

    let temp1 = Celsius 32.0;;

    Response

    val temp1 : temperature = Celsius 32.0

    F#

    let temp2 = Fahrenheit 98.6;;

    Response

    val temp2 : temperature = Fahrenheit 98.6

    F#

    let temp3 = Kelvin 5.0;;

    Response

    val temp3 : temperature = Kelvin 5.0

    Note:
    Although these are all different type of values, they are all of the type temperature.

  3. If you look at the values, you can see that in addition to the data, the type of the data was stored as well. Enter the following into the F# interactive console:

    F#

    temp1;;

    Response

    val it : temperature = Celsius 32.0

Task 3 – Using Pattern Matching in Discriminated Unions

In this task, you will see how to use pattern matching to deconstruct discriminated unions.

  1. Building on your temperature example, you will create functions to convert temperatures. Create these functions by entering these commands into the F# interactive console:

    F#

    let convertToFahrenheit x = match x with | Celsius x -> Fahrenheit (x * (9.0 / 5.0) + 32.0) | Fahrenheit x -> Fahrenheit x | Kelvin x -> Fahrenheit (x * (9.0 / 5.0) - 459.67);;

    Response

    val convertToFahrenheit : temperature -> temperature

  2. By calling these functions and passing in the temperature values you created earlier, you can see that they are able to perform pattern matching against the constructor of each union to determine which algorithm to apply.

    F#

    convertToFahrenheit temp1;;

    Response

    val it : temperature = Fahrenheit 89.6

Next Step

Summary