d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
9.8 KiB
9.8 KiB
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
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
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
// 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.