Data Structures

All the code of the samples can be down loaded here.

Types in functional programming are interesting because while they have many similarities to their imperative cousins they also have a number of subtle and not so subtle differences.

In imperative languages a data structure is typically represented as collection of memory addresses that you can read or write from in some specific way. C# programmers will of course be familiar with the concept of a class; this is one of the most commonly used types in the .NET framework. Typically a class will have a number of fields that represent its state and provide methods and properties that can be used to read and write an object's state.

While ultimately types in functional languages are still represented by a collection of memory addresses (and indeed classes in a .NET assembly) they differ considerably conceptually. The most important difference is that data structures in F# are immutable, that is, once they are created they can not be altered. This may sound awfully inhibiting as just about all programming problems require values to change from time to time. In reality it is not. Consider the System.String class. It is one of the most commonly used classes in the .NET framework and it is immutable. Types in functional languages work the same way as System.String does. If you need to change them then a copy is made, and if the old copy is no longer required then the reference is allowed to go out of scope and the unneeded copy is garbage collected. All this copying may sound terribly inefficient, but there are lots of clever things language designers can do under the hood to make it more efficient. Many of these issues are discussed in Chris Okasaki’s excellent book Purely Functional Data Structures which can be bought here.

This isn’t quite the whole story with F# as it is not a pure functional language and it does provide one native mutable type natively, the array; this is discussed in the collections tutorial here. It also provides access to classes written in other .NET languages which may be mutable.

F# has very rich syntax for defining types. This tutorial will examine how to define types, how to access their members and how to create instances of them. Collections are, of course, important data structures in F#, but they are not discussed here. They are given a tutorial to themselves which can be found here.

Perhaps the simplest type a user can define in F# is the option type. It is like an enum in that is a collection of values, but unlike an enum these values do not represent a number. It is defined using the keyword type, an identifier for the type name followed by an equals sign, and a vertical bar "|" separating a list of values. An illustration of this is shown below.

type morningBeverageType =
     | Tea
     | Coffee

Pattern matching with option types is very simple. You can use the pattern-matching constructs described in the functions tutorial and list the values in the option type. To demonstrate this, here is an example of a method that converts morningBeverageType into a string.

let morningBeverageTypeToString choice =
     match choice with
     | Tea -> "Cup of Tea"
     | Coffee -> "Mug of Coffee"

F# allows you to give names to values. This is useful when you want to ensure the value you are dealing with is actually what you think it is. There are many places where you use integer values in a program, so if a function or method takes three integer arguments it’s fairly easy to cause a bug by getting them in the wrong order. In F#, a type that gives a name to a value is called a constructor, which can be a little confusing if you're going from the world of object-oriented programming. An example of a constructor is shown below.

type drinkAmount = NumberOfMugs of int

The easiest way to retrieve a value from a constructor is use the pattern-matching construct. A function to convert the drinkAmount type to an integer is shown below.

let int_of_drinkAmount drinks = match drinks with NumberOfMugs x -> x

It very common to combine the above two types, and to illustrate this I’ve defined a temperature type which can be any of the three commonly used temperature scales or unknown.

type temperature =
     | Celicus of float
     | Fahrenheit of float
     | Kelvin of float
     | Unknown

Again, pattern-matching is generally used to deal with these types. I’ve defined a simple function to output a temperature as a string.

let temperatureToString temp =
     match temp with
     | Celicus x -> (string_of_float x) ^ " Degrees Celicus"
     | Fahrenheit x -> (string_of_float x) ^ " Degrees Fahrenheit"
     | Kelvin x -> (string_of_float x) ^ " Kelvin"
     | Unknown -> "Unknown"

Record types are F#'s way of representing sets of related data; They are similar to structs in C, because like structs in C, and unlike structs in C#, they cannot have member methods. To illustrate the syntax I’ve provided a record type that combines the three types defined above.

type morningBeverageDetails =
{
     beverage : morningBeverageType;
     amount : drinkAmount;
     temp : temperature;
}

Access to the members of a record type uses a very similar syntax to accessing members in C#. The name of the identifier is given followed by a dot "." and the name of the member. A function to convert a morningBeverageDetails record into a string is shown below.

let morningBeverageDetailsToString details =
     (morningBeverageTypeToString details.beverage) ^ " - " ^
     "Number of mugs: " ^ (string_of_int (int_of_drinkAmount (details.amount))) ^ " - " ^
     (temperatureToString details.temp)

Types need not always create new types; you can also give a new name to an existing type. This is generally used to give primitive types a more meaningful name when they are used inside record types. Here I give a new name to the string type and to a tuple of integers. A tuple is a collection of values that is similar to a record but where the members do not have names.

type name = string
type lottoNumbers = (int * int * int * int * int * int)

Now that I have given these types names, they can be used inside a record type. Here I illustrate this by defining a person record type.

type person =
{
     salutation : string;
     firstName : name;
     middleName : name option;
     lastName : name;
     bodyTemperature : temperature;
     morningPrefernce : morningBeverageDetails;
     numbers : lottoNumbers;
}

One thing to note about the person type is that the middleName member is listed as option. This says the middle name is optional, since not everyone has a middle name. The option type a native F# type. It is similar to saying that something can be null, but a more elegant representation, as it forces programmers to define what they want to do if the data is available and what they want to do if it is not. This can be seen in the function given below that turns a person into a string.

let personToString person =
     let middleName = match person.middleName with
     | Some x -> x ^ " "
     | None -> ""
     in
     let l1, l2, l3, l4, l5, l6 = person.numbers
     in
     person.salutation ^ " " ^ person.firstName ^ " " ^ middleName ^ person.lastName ^
     " - " ^ (temperatureToString person.bodyTemperature) ^
     " - " ^ (morningBeverageDetailsToString person.morningPrefernce) ^
     " - " ^ (string_of_int l1) ^ ", " ^
     (string_of_int l2) ^ ", " ^
     (string_of_int l3) ^ ", " ^
     (string_of_int l4) ^ ", " ^
     (string_of_int l5) ^ ", " ^
     (string_of_int l6)

It's also interesting to note how values from tuples can be assigned to identifiers using the let keyword. Simply list identifiers for all the values you are interested in in the correct order, if you wish to ignore some values from the tuple you can replace their identifiers with an underscore "_".