F#

Pattern Matching

Remarks#

Pattern Matching is a powerful feature of many functional languages as it often allows branching to be handled very succinctly compared to using multiple if/else if/else style statements. However given enough options and “when” guards, Pattern Matching can also become verbose and difficult to understand at a glance.

When this happens F#‘s Active Patterns can be a great way to give meaningful names to the matching logic, which simplifies the code and also enables reuse.

Matching Options

Pattern matching can be useful to handle Options:

let result = Some("Hello World")
match result with
| Some(message) -> printfn message
| None -> printfn "Not feeling talkative huh?"

Pattern matching checks the entire domain is covered

let x = true
match x with
| true -> printfn "x is true"

yields a warning

C:\Program Files (x86)\Microsoft VS Code\Untitled-1(2,7): warning FS0025: Incomplete pattern matches on this expression. For example, the value ‘false’ may indicate a case not covered by the pattern(s).

This is because not all of the possible bool values were covered.

bools can be explicitly listed but ints are harder to list out

let x = 5
match x with
| 1 -> printfn "x is 1"
| 2 -> printfn "x is 2"
| _ -> printfn "x is something else"

here we use the special _ character. The _ matches all other possible cases.

The _ can get you into trouble

consider a type we create ourselves it looks like this

type Sobriety = 
    | Sober
    | Tipsy
    | Drunk

We might write a match with expession that looks like this

match sobriety with
| Sober -> printfn "drive home"
| _ -> printfn "call an uber"

The above code makes sense. We are assuming if you aren’t sober you should call an uber so we use the _ to denote that

We later refactor our code to this

type Sobriety = 
    | Sober
    | Tipsy
    | Drunk
    | Unconscious

The F# compiler should give us a warning and prompt us to refactor our match expression to have the person seek medical attention. Instead the match expression silently treats the unconscious person as if they were only tipsy. The point is you should opt to explicitly list out cases when possible to avoid logic errors.

Cases are evaluated from top to bottom and the first match is used

Incorrect usage:

In the following snippet, the last match will never be used:

let x = 4
match x with
| 1 -> printfn "x is 1"
| _ -> printfn "x is anything that wasn't listed above"
| 4 -> printfn "x is 4"

prints

x is anything that wasn’t listed above

Correct usage:

Here, both x = 1 and x = 4 will hit their specific cases, while everything else will fall through to the default case _:

let x = 4
match x with
| 1 -> printfn "x is 1"
| 4 -> printfn "x is 4"
| _ -> printfn "x is anything that wasn't listed above"

prints

x is 4

When guards let you add arbitrary conditionals

type Person = {
    Age : int
    PassedDriversTest : bool }

let someone = { Age = 19; PassedDriversTest = true }

match someone.PassedDriversTest with
| true when someone.Age >= 16 -> printfn "congrats"
| true -> printfn "wait until you are 16"
| false -> printfn "you need to pass the test"

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow