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
- Basic Pattern Matching: Matching simple values and constants.
- Matching Tuples: Deconstructing tuples into their components.
- Matching Lists: Handling lists by matching their head and tail.
- Matching Records and Discriminated Unions: Deconstructing complex data types.
- Guards: Adding conditions to patterns.
- 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 valuex
.| 1 -> "One"
: Ifx
is 1, return "One".| 2 -> "Two"
: Ifx
is 2, return "Two".| 3 -> "Three"
: Ifx
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 intoa
andb
.- 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, andtail
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 ifn
is less than 0.n when n = 0
: Matches ifn
is 0.n when n > 0
: Matches ifn
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.
F# Programming Course
Module 1: Introduction to F#
Module 2: Core Concepts
- Data Types and Variables
- Functions and Immutability
- Pattern Matching
- Collections: Lists, Arrays, and Sequences
Module 3: Functional Programming
Module 4: Advanced Data Structures
Module 5: Object-Oriented Programming in F#
- Classes and Objects
- Inheritance and Interfaces
- Mixing Functional and Object-Oriented Programming
- Modules and Namespaces
Module 6: Asynchronous and Parallel Programming
Module 7: Data Access and Manipulation
Module 8: Testing and Debugging
- Unit Testing with NUnit
- Property-Based Testing with FsCheck
- Debugging Techniques
- Performance Profiling