Skip to content

Pattern matching F# Lists

Here are some simple examples I’ve come up with for working with lists in F#. These follow on from previous work looking at finding a value in a list and again this is definitely not efficient F# code. I’m just playing around in the language and trying to understand things.

First I tried walking a list using a recursive function and a match pattern. All I wanted to do was pop the head of the list, print it and process the remainder of the list.

The printfn statements in my example use the %A format specifier which provides pretty printing for any value. Also the ; symbol used to separate multiple expressions in a match rule causes the first expression to be evaluated and its result discarded before the second expression is evaluated and used as the result. This method can be used to execute side-affecting statements such as old fashioned debug printing.

#light;

let rec listWalk list =
  printfn "-----";
  match list with 
  | [] -> printfn "Empty List";
  | h::t -> printfn "Head is %A" h; printfn "Tail is %A" t; listWalk t;;

let s = "A string of words";;
let l = s |> String.split [' '];;

The list of words l is used in all the examples for this post.

> listWalk l;;
-----
Head is "A"
Tail is ["string"; "of"; "words"]
-----
Head is "string"
Tail is ["of"; "words"]
-----
Head is "of"
Tail is ["words"]
-----
Head is "words"
Tail is []
-----
Empty List
val it : unit = ()
>

The first match clause matches on an empty list ([]) and the second matches on a list construction. The head element is bound to the identifier h and the tail to the identifier t. Looking at the output from printfn, h is a value of type string (denoted by the surrounding double quotes) and t is a value of type list of string. Notice that when t is an empty list its string representation is the same as the pattern used to denote an empty list.

Next I wanted to a match rule to find the last element in the list, that is where there is a value for head and the tail is an empty list. So I added a rule that combined the previous two and matched on a list constructed with a head and an empty list.

let rec listWalk list =
 printfn "-----";
 match list with 
 | [] -> printfn "Empty List";
 | h::[] -> printfn "Last element is %A" h; 
 | h::t -> printfn "Head is %A" h; printfn "Tail is %A" t; listWalk t;; 

Using the same list of words as above.

> listWalk l;;
-----
Head is "A"
Tail is ["string"; "of"; "words"]
-----
Head is "string"
Tail is ["of"; "words"]
-----
Head is "of"
Tail is ["words"]
-----
Last element is "words"
val it : unit = ()

This time the rule for an empty list ([]) did not match because a recursive call was not made when a list with a single element was detected.

There is an easy mistake to make when using match patterns like this though. The patterns h::t and h::[] will both match the pattern of a list with one remaining element, as [] just means an empty list. So if the rules are sequenced incorrectly my new rule to detect the last element in the list will not work.

let rec listWalk list =
  printfn "-----";
  match list with 
  | [] -> printfn "Empty List";
  | h::t -> printfn "Head is %A" h; printfn "Tail is %A" t; listWalk t;
  | h::[] -> printfn "Last element is %A" h;;

Results in…

> listWalk l;;
-----
Head is "A"
Tail is ["string"; "of"; "words"]
-----
Head is "string"
Tail is ["of"; "words"]
-----
Head is "of"
Tail is ["words"]
-----
Head is "words"
Tail is []
-----
Empty List
val it : unit = ()
>

In fact the compiler is able to work this out outputs a warning when compiling the function.

warning FS0026: This rule will never be matched.

Of course if all I wanted to do was iterate over a list there are many functions in the List Module that would be a better choice. For example using one of the four iter(ation) functions.

let listWalk list =
  printfn "-----";
  list |> List.iter (fun e -> printfn "Element is %A" e);;
> > listWalk l;;
-----
Element is "A"
Element is "string"
Element is "of"
Element is "words"
val it : unit = ()
>

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*