Exercise 4: Lists

This exercise will demonstrate how F# works with lists. It will cover how F#’s implementation of a linked list is very different from its counterpart in imperative programming languages, explain the Head + Tail approach and demonstrate the abilities this approach gives your code through the use of recursive functions and pattern matching.

Task 1 – Creating Lists

In this task you will learn the syntax to create lists in F#.

Note:
F#, like all functional languages, implements its lists as linked lists. However, the implementation of linked lists in F# is a little different from that of imperative languages. Most imperative languages implement linked lists by creating nodes which contain a value and a pointer to the next node in the list. This makes it very easy to add items in the middle of the list. F# implements lists as Heads and Tails. Essentially, a list item node in F# consists of a value and a tail, which itself is another list.

For more information on how F# deals with lists, please refer to Dustin Campbell’s blog at https://diditwith.net/2008/03/03/WhyILoveFListsTheBasics.aspx

  1. One way to create lists in F# is to specify a list of item separated by double-colons (::) with the empty list ([]) at the end of the list. Technically, F# views this as concatenating lists: each element in the list is viewed as being concatenated to either another element or an empty list. The end result is the same; F# views the concatenated values as a list. The sample code below creates three lists; and empty list, a list with one item and a list with two items. Enter the following code into the F# interactive console:

    F#

    let emptyList = [];;

    Response

    val emptyList : ‘a list

    F#

    let listWithOneItem = "one" :: [];;

    Response

    val listWithOneItem : string list = ["one"]

    F#

    let listWithTwoItems = "one" :: "two" :: [];;

    Response

    val listWithTwoItems : string list = ["one"; "two"]

    Note:
    Notice that in all cases, F# was able to correctly infer the type of the list automatically. What is especially interesting is the case of the first list you created. What does ‘a mean? That simply means it is a generic list where the type will be inferred at usage time.

    Also notice that through this entire lab so far you have not actually needed to specify a type a single time. Because of this, a lot of developers mistakenly think F# is a dynamic language. It is not. It is very much a statically-typed language. But F#’s aggressive use of type inference removes a lot of the necessary of specifying types that exist with other languages.

  2. F# does not allow lists to contain different types. For example, the sample code below will not compile because it is trying to put strings and integers in the same list:

    F#

    let badList = "one" :: 2 :: 3.0 :: [];;

    Note:
    Because these values are not of the same type, F# cannot put them in the same list.

    Figure 3

    A bad list

  3. The syntax above is very helpful in concatenating lists, but it’s a little unwieldy for creating lists from scratch. F# offers a short-hand for creating lists. Placing semi-colon delimited values in between square brackets will define a list in F#. Enter the following code into the F# interactive console :

    F#

    let easyList = ["A"; "B"; "C"];;

    Response

    val easyList : string list= ["A"; "B"; "C"]

  4. Because of the nature of how F# implements lists, it is easy to combine two lists into a single list. The example below creates two lists and then combines them into a third list. Enter the following code into the F# interactive console:

    F#

    let firstList = ["A"; "B"; "C"];;

    Response

    val firstList : string list = ["A"; "B"; "C"]

    F#

    let secondList = ["1"; "2"; "3"];;

    Response

    val secondList : string list = ["1"; "2"; "3"]

    F# will create two lists with the appropriate values. Now, to combine these lists, F# uses the @ operator. Enter the following code into the F# interactive console:

    F#

    let combinedList = firstList @ secondList;;

    Response

    val combinedList : string list = ["A"; "B"; "C"; "1"; "2"; "3"]

    F# has created new list. To verify that the new list is the values of your combines list, you can examine its contents by entering the following into the interactive console:

    F#

    combinedList;;

    Response

    val it : string list = ["A"; "B"; "C"; "1"; "2"; "3"]

Next Step

Exercise 5: Pattern Matching and Recursion