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: OtherExplanation
match x with: Starts the pattern matching on the valuex.| 1 -> "One": Ifxis 1, return "One".| 2 -> "Two": Ifxis 2, return "Two".| 3 -> "Three": Ifxis 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 zeroExplanation
(a, b): Deconstructs the tuple intoaandb.- 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: 10Explanation
[]: Matches an empty list.head :: tail: Deconstructs the list into its head and tail.headis the first element, andtailis 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 25Discriminated 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.0Guards
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: PositiveExplanation
n when n < 0: Matches ifnis less than 0.n when n = 0: Matches ifnis 0.n when n > 0: Matches ifnis 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: falsePractical 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 dayExercise 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 planeSummary
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
