Pattern matching is a powerful feature in F# that allows you to deconstruct data structures and perform actions based on their shape and content. It is a fundamental concept in functional programming and is used extensively in F# for control flow and data manipulation.

Key Concepts

  1. Basic Pattern Matching: Matching simple values and constants.
  2. Matching Tuples: Deconstructing tuples into their components.
  3. Matching Lists: Handling lists by matching their head and tail.
  4. Matching Records and Discriminated Unions: Deconstructing complex data types.
  5. Guards: Adding conditions to patterns.
  6. Wildcard Patterns: Ignoring parts of the data structure.

Basic Pattern Matching

Pattern matching in F# is typically done using the match keyword. Here is a simple example:

let describeNumber x =
    match x with
    | 1 -> "One"
    | 2 -> "Two"
    | 3 -> "Three"
    | _ -> "Other"

printfn "%s" (describeNumber 2)  // Output: Two
printfn "%s" (describeNumber 5)  // Output: Other

Explanation

  • match x with: Starts the pattern matching on the value x.
  • | 1 -> "One": If x is 1, return "One".
  • | 2 -> "Two": If x is 2, return "Two".
  • | 3 -> "Three": If x is 3, return "Three".
  • | _ -> "Other": The underscore _ is a wildcard pattern that matches any value not previously matched.

Matching Tuples

Tuples can be deconstructed in pattern matching:

let addTuple (a, b) =
    match (a, b) with
    | (0, 0) -> "Both are zero"
    | (0, _) -> "First is zero"
    | (_, 0) -> "Second is zero"
    | _ -> "Neither is zero"

printfn "%s" (addTuple (0, 0))  // Output: Both are zero
printfn "%s" (addTuple (0, 5))  // Output: First is zero
printfn "%s" (addTuple (3, 0))  // Output: Second is zero
printfn "%s" (addTuple (3, 4))  // Output: Neither is zero

Explanation

  • (a, b): Deconstructs the tuple into a and b.
  • Patterns like (0, 0), (0, _), and (_, 0) match specific shapes of the tuple.

Matching Lists

Lists can be matched by their head and tail:

let rec sumList lst =
    match lst with
    | [] -> 0
    | head :: tail -> head + sumList tail

printfn "%d" (sumList [1; 2; 3; 4])  // Output: 10

Explanation

  • []: Matches an empty list.
  • head :: tail: Deconstructs the list into its head and tail. head is the first element, and tail is the rest of the list.

Matching Records and Discriminated Unions

Records

type Person = { Name: string; Age: int }

let describePerson person =
    match person with
    | { Name = "Alice"; Age = 30 } -> "Alice is 30"
    | { Name = name; Age = age } -> sprintf "%s is %d" name age

let alice = { Name = "Alice"; Age = 30 }
let bob = { Name = "Bob"; Age = 25 }

printfn "%s" (describePerson alice)  // Output: Alice is 30
printfn "%s" (describePerson bob)    // Output: Bob is 25

Discriminated Unions

type Shape =
    | Circle of float
    | Rectangle of float * float

let area shape =
    match shape with
    | Circle radius -> Math.PI * radius * radius
    | Rectangle (width, height) -> width * height

printfn "%f" (area (Circle 5.0))         // Output: 78.539816
printfn "%f" (area (Rectangle (4.0, 5.0))) // Output: 20.0

Guards

Guards add conditions to patterns:

let classifyNumber x =
    match x with
    | n when n < 0 -> "Negative"
    | n when n = 0 -> "Zero"
    | n when n > 0 -> "Positive"

printfn "%s" (classifyNumber -5)  // Output: Negative
printfn "%s" (classifyNumber 0)   // Output: Zero
printfn "%s" (classifyNumber 10)  // Output: Positive

Explanation

  • n when n < 0: Matches if n is less than 0.
  • n when n = 0: Matches if n is 0.
  • n when n > 0: Matches if n is greater than 0.

Wildcard Patterns

Wildcard patterns ignore parts of the data structure:

let isZero x =
    match x with
    | 0 -> true
    | _ -> false

printfn "%b" (isZero 0)  // Output: true
printfn "%b" (isZero 5)  // Output: false

Practical Exercises

Exercise 1: Matching Days of the Week

Write a function that takes a day of the week (as a string) and returns whether it is a weekday or weekend.

let classifyDay day =
    match day with
    | "Saturday" | "Sunday" -> "Weekend"
    | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" -> "Weekday"
    | _ -> "Invalid day"

printfn "%s" (classifyDay "Monday")    // Output: Weekday
printfn "%s" (classifyDay "Saturday")  // Output: Weekend
printfn "%s" (classifyDay "Funday")    // Output: Invalid day

Exercise 2: Deconstructing Points

Write a function that takes a tuple representing a point (x, y) and returns a string describing its position relative to the origin.

let describePoint (x, y) =
    match (x, y) with
    | (0, 0) -> "Origin"
    | (0, _) -> "On the Y-axis"
    | (_, 0) -> "On the X-axis"
    | _ -> "In the plane"

printfn "%s" (describePoint (0, 0))   // Output: Origin
printfn "%s" (describePoint (0, 5))   // Output: On the Y-axis
printfn "%s" (describePoint (3, 0))   // Output: On the X-axis
printfn "%s" (describePoint (3, 4))   // Output: In the plane

Summary

In this section, we covered the basics of pattern matching in F#. We learned how to match simple values, tuples, lists, records, and discriminated unions. We also explored the use of guards and wildcard patterns. Pattern matching is a versatile tool that simplifies control flow and data manipulation in F#. In the next section, we will delve into collections, including lists, arrays, and sequences.

© Copyright 2024. All rights reserved