パターンマッチ

値の構造に基づいて分岐処理を行う制御構文で、switch 文の強化版

プログラミング型システム

パターンマッチとは

パターンマッチは、値の構造に基づいて分岐処理を行う制御構文である。単純な値の比較だけでなく、型の判別、構造の分解、条件の組み合わせを 1 つの構文で表現できる。Rust の match、Haskell の case が代表例。

Rust の match

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
    }
    // 全パターンを網羅しないとコンパイルエラー
}

TypeScript の Discriminated Union

type Result<T> =
  | { ok: true; value: T }
  | { ok: false; error: string };

function handle(result: Result<number>) {
  if (result.ok) {
    console.log(result.value); // TypeScript が value の存在を保証
  } else {
    console.error(result.error); // TypeScript が error の存在を保証
  }
}

網羅性チェック (Exhaustiveness Check)

type Color = 'red' | 'green' | 'blue';

function toHex(color: Color): string {
  switch (color) {
    case 'red': return '#ff0000';
    case 'green': return '#00ff00';
    case 'blue': return '#0000ff';
    // 'blue' を忘れると TypeScript がエラーを出す
  }
}

// never 型で網羅性を強制
function assertNever(x: never): never {
  throw new Error(`Unexpected: ${x}`);
}

Rust の高度なパターン

match value {
    0 => println!("zero"),
    1..=9 => println!("single digit"),     // 範囲パターン
    n if n % 2 == 0 => println!("even"),   // ガード条件
    _ => println!("other"),                 // ワイルドカード
}

// 構造体の分解
match point {
    Point { x: 0, y } => println!("on y-axis at {y}"),
    Point { x, y: 0 } => println!("on x-axis at {x}"),
    Point { x, y } => println!("({x}, {y})"),
}

if-else チェーンとの比較

観点 if-else パターンマッチ
網羅性チェック なし コンパイラが保証
構造の分解 手動 自動
可読性 条件が増えると低下 構造的で読みやすい

Lambda ハンドラでの活用

type Event =
  | { source: 'api'; httpMethod: string; path: string }
  | { source: 'sqs'; Records: SQSRecord[] }
  | { source: 'schedule' };

function route(event: Event) {
  switch (event.source) {
    case 'api': return handleApi(event);
    case 'sqs': return handleSqs(event);
    case 'schedule': return handleSchedule();
  }
}

全体像を把握するには関連書籍も有用。

関連用語