# Pattern Matching

## Problem

Complex conditional logic with deeply nested if/else chains or type-checking cascades makes code hard to read, maintain, and extend. Adding a new case requires modifying multiple places.

## Solution

Use **pattern matching** to declaratively branch based on the structure, type, or value of data. Each pattern is a concise description of what to match, and the corresponding handler executes when the pattern fits. This centralizes dispatch logic and makes all cases visible at a glance.

## When to Use

- Dispatching on the shape or type of data (tagged unions, variant types)
- Parsing or interpreting structured data (ASTs, protocols, messages)
- Replacing long if/elif/else or switch chains
- Destructuring complex data to extract relevant parts

## When to Avoid

- Simple two-branch conditions where if/else is clearer
- When polymorphism (method dispatch) is the idiomatic solution
- Extremely performance-sensitive paths where match overhead matters

## Pseudocode

```
match shape:
    case Circle(radius):
        area = pi * radius^2
    case Rectangle(width, height):
        area = width * height
    case Triangle(base, height):
        area = 0.5 * base * height
    case _:
        error("Unknown shape")

print("Area: " + area)
```

## Python

```python
from dataclasses import dataclass
import math

# Define shape types
@dataclass
class Circle:
    radius: float

@dataclass
class Rectangle:
    width: float
    height: float

@dataclass
class Triangle:
    base: float
    height: float

def compute_area(shape) -> float:
    match shape:
        case Circle(radius=r):
            return math.pi * r * r
        case Rectangle(width=w, height=h):
            return w * h
        case Triangle(base=b, height=h):
            return 0.5 * b * h
        case _:
            raise ValueError(f"Unknown shape: {shape}")

def describe_value(value) -> str:
    match value:
        case 0:
            return "zero"
        case int(n) if n > 0:
            return f"positive int: {n}"
        case int(n):
            return f"negative int: {n}"
        case float(f) if f != f:  # NaN check
            return "not a number"
        case float(f):
            return f"float: {f}"
        case str(s) if len(s) == 0:
            return "empty string"
        case str(s):
            return f"string: '{s}'"
        case [first, *rest]:
            return f"list starting with {first}, {len(rest)} more"
        case {"type": t, "value": v}:
            return f"dict with type={t}, value={v}"
        case _:
            return f"unknown: {value}"

def main() -> None:
    # Shape matching
    print("=== Shape Areas ===")
    shapes = [Circle(5), Rectangle(4, 6), Triangle(3, 8)]
    for shape in shapes:
        area = compute_area(shape)
        print(f"{shape} -> area = {area:.2f}")

    # Value matching
    print("\n=== Value Matching ===")
    values = [0, 42, -7, 3.14, "", "hello", [1, 2, 3], {"type": "event", "value": 99}]
    for v in values:
        print(f"  {v!r:30s} -> {describe_value(v)}")

    # Command parsing with pattern matching
    print("\n=== Command Parsing ===")
    commands = [
        ("move", 10, 20),
        ("resize", 100, 200),
        ("color", "red"),
        ("quit",),
    ]
    for cmd in commands:
        match cmd:
            case ("move", x, y):
                print(f"  Moving to ({x}, {y})")
            case ("resize", w, h):
                print(f"  Resizing to {w}x{h}")
            case ("color", c):
                print(f"  Setting color to {c}")
            case ("quit",):
                print("  Quitting")
            case _:
                print(f"  Unknown command: {cmd}")

if __name__ == "__main__":
    main()
```

## Go

```go
package main

import (
	"fmt"
	"math"
)

// Shape types
type Shape interface {
	shapeTag()
}

type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
type Triangle struct{ Base, Height float64 }

func (Circle) shapeTag()    {}
func (Rectangle) shapeTag() {}
func (Triangle) shapeTag()  {}

// Pattern matching via type switch
func computeArea(s Shape) float64 {
	switch shape := s.(type) {
	case Circle:
		return math.Pi * shape.Radius * shape.Radius
	case Rectangle:
		return shape.Width * shape.Height
	case Triangle:
		return 0.5 * shape.Base * shape.Height
	default:
		panic(fmt.Sprintf("Unknown shape: %T", s))
	}
}

// Value type matching
func describeValue(v interface{}) string {
	switch val := v.(type) {
	case int:
		if val == 0 {
			return "zero"
		} else if val > 0 {
			return fmt.Sprintf("positive int: %d", val)
		}
		return fmt.Sprintf("negative int: %d", val)
	case float64:
		return fmt.Sprintf("float: %.2f", val)
	case string:
		if len(val) == 0 {
			return "empty string"
		}
		return fmt.Sprintf("string: '%s'", val)
	case []int:
		if len(val) > 0 {
			return fmt.Sprintf("int slice starting with %d, %d more", val[0], len(val)-1)
		}
		return "empty int slice"
	case nil:
		return "nil"
	default:
		return fmt.Sprintf("unknown: %v", val)
	}
}

// Command types
type Command interface {
	cmdTag()
}

type MoveCmd struct{ X, Y int }
type ResizeCmd struct{ W, H int }
type ColorCmd struct{ Color string }
type QuitCmd struct{}

func (MoveCmd) cmdTag()   {}
func (ResizeCmd) cmdTag() {}
func (ColorCmd) cmdTag()  {}
func (QuitCmd) cmdTag()   {}

func executeCommand(cmd Command) {
	switch c := cmd.(type) {
	case MoveCmd:
		fmt.Printf("  Moving to (%d, %d)\n", c.X, c.Y)
	case ResizeCmd:
		fmt.Printf("  Resizing to %dx%d\n", c.W, c.H)
	case ColorCmd:
		fmt.Printf("  Setting color to %s\n", c.Color)
	case QuitCmd:
		fmt.Println("  Quitting")
	}
}

func main() {
	// Shape matching
	fmt.Println("=== Shape Areas ===")
	shapes := []Shape{
		Circle{Radius: 5},
		Rectangle{Width: 4, Height: 6},
		Triangle{Base: 3, Height: 8},
	}
	for _, s := range shapes {
		area := computeArea(s)
		fmt.Printf("%+v -> area = %.2f\n", s, area)
	}

	// Value matching
	fmt.Println("\n=== Value Matching ===")
	values := []interface{}{0, 42, -7, 3.14, "", "hello", []int{1, 2, 3}, nil}
	for _, v := range values {
		fmt.Printf("  %-20v -> %s\n", v, describeValue(v))
	}

	// Command matching
	fmt.Println("\n=== Command Parsing ===")
	commands := []Command{
		MoveCmd{10, 20},
		ResizeCmd{100, 200},
		ColorCmd{"red"},
		QuitCmd{},
	}
	for _, cmd := range commands {
		executeCommand(cmd)
	}
}
```

## JavaScript

```javascript
// Shape classes
class Circle {
  constructor(radius) {
    this.type = "circle";
    this.radius = radius;
  }
}
class Rectangle {
  constructor(width, height) {
    this.type = "rectangle";
    this.width = width;
    this.height = height;
  }
}
class Triangle {
  constructor(base, height) {
    this.type = "triangle";
    this.base = base;
    this.height = height;
  }
}

// Pattern matching via destructuring + switch
function computeArea(shape) {
  switch (shape.type) {
    case "circle": {
      const { radius } = shape;
      return Math.PI * radius * radius;
    }
    case "rectangle": {
      const { width, height } = shape;
      return width * height;
    }
    case "triangle": {
      const { base, height } = shape;
      return 0.5 * base * height;
    }
    default:
      throw new Error(`Unknown shape: ${JSON.stringify(shape)}`);
  }
}

// Value matching with type checks + destructuring
function describeValue(value) {
  if (value === null || value === undefined) return "nil";
  if (typeof value === "number" && Number.isInteger(value)) {
    if (value === 0) return "zero";
    if (value > 0) return `positive int: ${value}`;
    return `negative int: ${value}`;
  }
  if (typeof value === "number") return `float: ${value}`;
  if (typeof value === "string") {
    return value.length === 0 ? "empty string" : `string: '${value}'`;
  }
  if (Array.isArray(value) && value.length > 0) {
    const [first, ...rest] = value;
    return `array starting with ${first}, ${rest.length} more`;
  }
  if (typeof value === "object" && "type" in value && "value" in value) {
    const { type, value: v } = value;
    return `object with type=${type}, value=${v}`;
  }
  return `unknown: ${value}`;
}

// Command matching
function executeCommand(cmd) {
  switch (cmd.action) {
    case "move": {
      const { x, y } = cmd;
      console.log(`  Moving to (${x}, ${y})`);
      break;
    }
    case "resize": {
      const { w, h } = cmd;
      console.log(`  Resizing to ${w}x${h}`);
      break;
    }
    case "color": {
      const { color } = cmd;
      console.log(`  Setting color to ${color}`);
      break;
    }
    case "quit":
      console.log("  Quitting");
      break;
    default:
      console.log(`  Unknown command: ${JSON.stringify(cmd)}`);
  }
}

function main() {
  // Shape matching
  console.log("=== Shape Areas ===");
  const shapes = [new Circle(5), new Rectangle(4, 6), new Triangle(3, 8)];
  for (const shape of shapes) {
    const area = computeArea(shape);
    console.log(`${shape.type}(${JSON.stringify(shape)}) -> area = ${area.toFixed(2)}`);
  }

  // Value matching
  console.log("\n=== Value Matching ===");
  const values = [0, 42, -7, 3.14, "", "hello", [1, 2, 3], { type: "event", value: 99 }, null];
  for (const v of values) {
    const repr = JSON.stringify(v) ?? "null";
    console.log(`  ${repr.padEnd(30)} -> ${describeValue(v)}`);
  }

  // Command matching
  console.log("\n=== Command Parsing ===");
  const commands = [
    { action: "move", x: 10, y: 20 },
    { action: "resize", w: 100, h: 200 },
    { action: "color", color: "red" },
    { action: "quit" },
  ];
  for (const cmd of commands) {
    executeCommand(cmd);
  }
}

main();
```

## Related Patterns

- **Visitor** — visitor achieves type-based dispatch through double dispatch; pattern matching achieves it through structural matching.
- **Strategy** — each match branch is effectively a strategy for handling one case.
- **Interpreter** — interpreters evaluate AST nodes; pattern matching over node types is the natural way to implement interpretation.
