Pattern Matching on Data Structures
Pattern matching is a powerful feature in F# that allows you to extract and work with the values in data structures such as tuples, lists, arrays, discriminated unions, and records. In this chapter, we’ll explore how to use pattern matching to work with these data structures.
Matching on tuples allows you to match a tuple against one or more patterns. Let’s say we have a function that returns a tuple of two integers, and we want to write a function that takes the tuple and returns the sum of the two integers. We can use pattern matching on tuples as follows:
let sumTuple (x, y) =
match (x, y) with
| (a, b) -> a + b
In this example, we define a function sumTuple
that takes a tuple of two integers (x, y)
. We then use pattern matching to match the tuple (x, y)
against a pattern of the form (a, b)
. The pattern binds the values of x
and y
to a
and b
, respectively, and then returns their sum.
Matching on lists allows you to match a list against one or more patterns. Let’s say we want to write a function that takes a list of integers and returns the sum of the even integers. We can use pattern matching on lists as follows:
let sumEvens xs =
let rec sum acc = function
| [] -> acc
| x::xs when x % 2 = 0 -> sum (acc + x) xs
| _::xs -> sum acc xs
sum 0 xs
In this example, we define a function sumEvens
that takes a list of integers xs
. We then define an inner recursive function sum
that takes an accumulator acc
and a list xs
. We use pattern matching to match the list xs
against three patterns: []
, x::xs when x % 2 = 0
, and _::xs
. The first pattern matches an empty list and returns the accumulator. The second pattern matches a non-empty list where the head x
is even, and it adds x
to the accumulator before calling sum
recursively on the tail xs
. The third pattern matches a non-empty list where the head _
is odd, and it discards the head and calls sum
recursively on the tail xs
.
Discriminated Unions (DUs) are often used in F# to model complex data structures. One of the most powerful features of DUs is pattern matching. Pattern matching is a powerful tool that allows you to match values against patterns and perform different actions depending on the pattern that is matched.
In F#, you can use pattern matching with DUs to extract the values associated with each case of the DU. Let’s take the example of the Sound
DU:
type Note =
| A
| ASharp
| B
| C
| CSharp
| D
| DSharp
| E
| F
| FSharp
| G
| GSharp
type Octave =
| One
| Two
| Three
type Sound =
| Pause
| Tone of Note * Octave
We can use pattern matching to extract the values associated with the Tone
case:
let pause = Pause
let tone = Tone(FSharp, Two)
match tone with
| Tone(note, octave) -> printfn "Note: %A, Octave: %A" note octave
| Pause -> printfn "Pause"
// Output: Note: FSharp, Octave: Two
In this example, we have defined a value of Sound called tone
, which is a Tone
consisting of a F# note and the second octave. We then use the match
keyword to pattern match against the tone
value. The first case that we define in the match
expression is Tone(note, octave)
, which matches against any value that is a Tone
case of the Sound
DU. We then use printfn
to print out the note and octave values.
If the value of tone
is a Pause
, the second case of the match
expression will match and print out “Pause” to the console.
Records are another data structure that can be pattern matched in F#. Let’s take the example of the Card
record that we defined earlier:
type Card = { Suit: Suit; Rank: Rank }
We can use pattern matching to extract the Suit
and Rank
values from a Card
:
let card = { Suit = Spades; Rank = Ace(1) }
match card with
| { Suit = suit; Rank = rank } -> printfn "Suit: %A, Rank: %A" suit rank
// Output: Suit: Spades, Rank: Ace 1
In this example, we have defined a value of Card
called card
, which is a Spades Ace. We then use the match
keyword to pattern match against the card
value. The first case that we define in the match
expression is { Suit = suit; Rank = rank }
, which matches against any value that is a Card
record. We then use printfn
to print out the suit and rank values.
Pattern matching on records can be useful when you want to extract specific fields from a record and perform some action on them. It is also useful when working with large records that have many fields, as you can extract only the fields that you need.
Arrays are another data structure that can be pattern matched in F#. Let’s say we want to write a function that takes an array of integers and returns the sum of the even integers. We can use pattern matching on arrays as follows:
let sumEvens arr =
let mutable sum = 0
for i in 0 .. arr.Length - 1 do
match arr.[i] with
| x when x % 2 = 0 -> sum <- sum + x
| _ -> ()
sum
In this example, we define a function sumEvens
that takes an array of integers arr
. We then define a mutable variable sum
to hold the running sum of even integers. We use a for
loop to iterate over the array indices, and we use pattern matching to match the value at each index against two patterns: x when x % 2 = 0
, which matches even integers and adds them to the sum
, and _
, which matches all other values and does nothing.
After iterating over all the elements in the array, we return the final value of the sum
.