Skip to content

Pattern Matching

Pattern matching compares values against patterns using switch, if case, for case, and other constructs, enabling expressive and concise code for data decomposition and control flow.

Basic Patterns

  • Value-Binding: Extract values into variables.
  • Wildcard: Ignore values with _.
  • Literal: Match specific values.

Example: Basic Switch:

swift
let point = (x: 1, y: 0)
switch point {
case (0, 0):
    print("Origin")
case let (x, _):
    print("X: \(x)")
default:
    print("Elsewhere")
}

Enum Patterns

Match enum cases, including associated values.

Example:

swift
enum Result {
    case success(String)
    case failure(Error)
}

let result = Result.success("Done")
switch result {
case let .success(message):
    print("Success: \(message)")
case let .failure(error):
    print("Error: \(error)")
}

Optional Patterns

Match optional values with .some or .none.

Example:

swift
let value: Int? = 42
if case .some(let x) = value {
    print("Value: \(x)") // 42
}

if case .none = value {
    print("No value")
}

Tuple Patterns

Decompose tuples into components.

Example:

swift
let status = (code: 200, message: "OK")
switch status {
case (200, let msg):
    print("Success: \(msg)")
case (let code, _):
    print("Code: \(code)")
}

Range Patterns

Match values within ranges.

Example:

swift
let score = 85
switch score {
case 90...100:
    print("A")
case 80..<90:
    print("B")
default:
    print("C or below")
}

Expression Patterns

Match custom expressions with ~= operator.

Example:

swift
struct StatusCode {
    let value: Int
    static func ~= (pattern: Range<Int>, code: StatusCode) -> Bool {
        return pattern.contains(code.value)
    }
}

let code = StatusCode(value: 404)
switch code {
case 200..<300:
    print("Success")
case 400..<500:
    print("Client Error") // "Client Error"
default:
    print("Other")
}

Type-Casting Patterns

Use is and as for type checking and casting.

Example:

swift
let items: [Any] = [1, "Hello", 3.14]
for item in items {
    switch item {
    case is Int:
        print("Integer")
    case let str as String:
        print("String: \(str)")
    case let num as Double:
        print("Double: \(num)")
    default:
        print("Unknown")
    }
}

Where Clauses

Add conditions to patterns.

Example:

swift
switch point {
case let (x, y) where x == y:
    print("On diagonal")
case let (x, y) where x > y:
    print("Above diagonal")
default:
    print("Below or elsewhere")
}

For Case Loops

Filter iterations with patterns.

Example:

swift
let results = [Result.success("A"), Result.failure(NSError()), Result.success("B")]
for case let .success(message) in results {
    print("Success: \(message)") // "A", "B"
}

If Case Statements

Match patterns in conditional statements.

Example:

swift
if case let .success(message) = result {
    print("Success: \(message)")
}

Nested Patterns

Combine patterns for complex matching.

Example:

swift
enum Event {
    case userAction(userID: String, action: String)
    case systemEvent(code: Int)
}

let event = Event.userAction(userID: "u1", action: "login")
switch event {
case let .userAction(id, action) where action == "login":
    print("User \(id) logged in")
case .userAction(_, _):
    print("Other user action")
case .systemEvent(let code):
    print("System event: \(code)")
}

Best Practices

  • Exhaustive Switches: Avoid default for enums to catch changes.
  • Clear Patterns: Use value-binding for readability.
  • Where Clauses: Filter complex conditions.
  • Type Casting: Prefer as? in patterns for safety.
  • Nested Patterns: Simplify with helper functions if complex.
  • Test Patterns: Verify edge cases in unit tests.

Troubleshooting

  • Non-Exhaustive Switch: Add missing cases or default.
  • Pattern Mismatch: Check pattern compatibility with value.
  • Type Cast Failures: Use is to verify types.
  • Complex Patterns: Refactor into smaller switches.
  • Performance: Optimize patterns for large datasets.

Example: Comprehensive Pattern Matching

swift
enum Transaction {
    case payment(amount: Double, to: String)
    case refund(amount: Double, reason: String)
    case transfer(from: String, to: String, amount: Double)
}

let transactions: [Transaction] = [
    .payment(amount: 50.0, to: "Alice"),
    .refund(amount: 25.0, reason: "Defective"),
    .transfer(from: "Bob", to: "Charlie", amount: 100.0)
]

for transaction in transactions {
    switch transaction {
    case let .payment(amount, to) where amount > 100:
        print("Large payment of \(amount) to \(to)")
    case .payment(let amount, let to):
        print("Payment of \(amount) to \(to)")
    case .refund(let amount, let reason):
        print("Refund of \(amount) for \(reason)")
    case .transfer(let from, let to, let amount) where from == to:
        print("Invalid transfer of \(amount)")
    case .transfer(let from, let to, let amount):
        print("Transfer of \(amount) from \(from) to \(to)")
    }
}

Released under the MIT License.