/note/tech

代数的データ型って何が嬉しいの?

要約:

■ 1. 概要・前提とゴール

  • 発表者: 梶川 琢馬 (@kajitack) - 株式会社TechBowl VPoT / TechTrain開発・メンター
  • 発表イベント: フロントエンド・PHPカンファレンス北海道2026
  • 対象者: TypeScriptおよびPHPを使う開発者で、「代数的データ型」を知らない方
  • ゴール: 代数的データ型の雰囲気を掴み、フロントエンドのUI構造とPHPでのエラーハンドリングを代数的データ型で表現する方法を理解する

■ 2. 型に対する考え方

  • 型は「ビット列の解釈」としてではなく「集合」として捉える
    • ビット列の解釈: データのサイズや扱い方を意識した型定義
    • 集合: 「取りうる値はこれだけ」と値の範囲を決め、実行前に処理を判断するための型定義
  • TypeScript公式でも「Types as Sets」として紹介されている
  • 型を書くこと = 集合を絞ること
    • 例: string型(ありえない値の組み合わせが膨大)→ OrderStatus型("pending" | "paid" | "shipped")でドメイン知識により絞り込む
  • 型(集合)を厳密に宣言することの利点:
    • 値の範囲を厳密に決めることで、ありえない状態を未然に防ぐ
    • 実行時にチェックする防御的プログラミングが不要になる
    • 補完やガードレールとしての開発者体験が向上する
    • テストの記述量を削減できる
  • 設計方針: ドメインの知識で型を絞り「出来ていいことだけを出来るようにする」(t-wada「予防に勝る防御なし」より)

■ 3. 代数的データ型 (ADT: Algebraic Data Types)

  • 掛け算や足し算のように型を組み立てる概念
  • 積(AND)型:
    • 直積型(Product Type): 複数の型の値を同時に保持する
    • 代表例: Class / Struct / オブジェクト型 / タプル
    • 値の数は「各フィールドの値の数の掛け算」で増える(例: name × age)
  • 和(OR)型:
    • 列挙型(Enumerated Type): 複数の値の選択肢のうち1つをとる、ただし構造(直積)を持てない
    • 直和型(Sum Type): 直積を持てる列挙型。代表例: Discriminated Union / Tagged Union / Sealed クラス
    • 値の数は「各ケースの値の数の足し算」で増える
    • kind のような共通フィールドが判別子(タグ)となり、その値でケースを見分け型が絞り込まれる
  • データの仕様は突き詰めると「かつ」と「または」でできている
  • 特に和(OR)が型を組み合わせる際に強力な手段となる
  • 継承と直和型の違い:
    • 継承: 親が子を知らないため、集合で「全部で何通りか」が確定しない(開いている)
    • 直和型(Sealed クラス): 型で子を決めておくことができるため集合が閉じる
  • Make Illegal States Unrepresentable: 仕様上ありえない状態は表現しない、コンパイラや静的解析がそれを理解する

■ 4. 実践編: フロントエンド - UIの状態とデータのモデリング

  • データ取得状態を boolean/null の積(直積)で管理する問題:
    • isLoading / error / data の3つのフラグで 2×2×2 = 8通りの組み合わせが発生する
    • 有効な状態は「未取得」「読み込み中」「エラー」「取得済み」の4通りのみ
    • 「読み込み中なのにエラーもデータもある」などの不正な状態が表現可能になってしまう
  • boolean型はたいてい「別の型」の成れの果て("That boolean should probably be something else"):
    • isConfirmed: booleanconfirmedAt: Date | null(一度きりの出来事の情報を保持)
    • isAdmin: booleanrole: "admin" | "editor" | "viewer"(状態が増えても網羅チェックが効く)
    • check(user): booleanAllowed | NotPermitted(reason)(boolean blindnessの回避)
  • UIの状態を Discriminated Union で表現:
    • useState 3本の直積をやめ、直和1本(AsyncData型)で持つ
    • AsyncData = { kind: "notAsked" } | { kind: "loading" } | { kind: "success"; data: Data } | { kind: "error"; error: Error }
    • 取りうる4状態のみをぴったり表現できる
  • never を使った網羅チェック(Exhaustiveness check)パターン:
    • switch文のdefaultで const _exhaustiveCheck: never = state とすることで、ケース漏れを型エラーとして検出する
  • 構造が異なるデータの集まりを Discriminated Union で表現:
    • 例: チャットメッセージ(botText / userText / tagSelect / mentorCards)を1つのChatMessage型で表現
    • 各ケース固有のプロパティへの誤アクセスを型エラーとして防止できる
    • 直和の中に直和を入れ子にして複雑な状態も表現できる
  • 描画ロジックでも同じ網羅チェックが効く:
    • switch文で各ケースのコンポーネントを返す構造にすることで、種別追加時の描画コード修正漏れをコンパイラが検出する
  • フロントエンド編まとめ:
    • UIの状態をフラグ(直積)で持つとありえない状態が混ざる
    • 状態も、構造が異なる要素の集まりも、直和(判別可能ユニオン)で列挙する
    • exhaustive check で網羅的な型チェックが可能になる

■ 5. 実践編: PHP - ドメインルールと整合性を守る

  • エラーもドメインの一部としてモデル化する(「関数型ドメインモデリング」より):
    • ドメインをモデル化する際はプリミティブ型を使わず、ドメインに特化した型を作成する
    • エラーも同様に扱い、特別な対応が必要なエラーの種類ごとに個別のケースを用意する
  • 例外は型シグネチャに現れない問題:
    • 例外は副作用であり、実行するまで何が起きるか分からない
    • 処理の失敗は「例外」ではなく、成功と同様に考慮すべき「結果」として扱う
  • Result型:
    • 成功か失敗か、どちらか一方を表す直和型
    • Result<T, E> = Ok(T) | Err(E)(成功なら Ok が値 T を持ち、失敗なら Err がエラー E を持つ)
    • 失敗を「投げる」のではなく、値として「返す」
    • PHPには標準でResult型がない
  • PHPでのResult型実装:
    • Sealed クラス(直和型)を PHPStan(静的解析)の @phpstan-sealed アノテーションで実現
    • interface Result@phpstan-sealed Ok|Err を付与し実装を特定クラスのみに限定する
    • final readonly class Ok implements Resultfinal readonly class Err implements Result で構成
  • Result型を使うとエラー分岐も網羅できる:
    • match式でエラーケース(OrderError::UserNotFound, OrderError::OutOfStock)を分岐する
    • ケース漏れはPHPStanが検出する
    • 失敗が「ビジネスロジックの値」になる
  • PHPのADT推進の動き:
    • RFC が進行中(Enumerations, Tagged Unions, Pattern Matching "is" keyword など段階的に実装予定)
    • enumはその一歩であり、Result型や直和型がいつかサポートされる可能性がある
  • PHP編まとめ:
    • 想定できる失敗は「例外」ではなく、Result型で値として返す
    • データ付きの直和は Sealed クラスで作り、静的解析で閉じて絞り込む
    • エラー分岐は match で網羅し、ケース漏れは静的解析が検出する
    • ADT の RFC が進行中であり、PHPの型アップデートに注目

■ 6. 全体まとめ

  • 型を「集合」として捉えると設計の武器になる
  • 厳密な型は補完・静的解析・AIが読み取ってくれる
  • 代数的データ型 = 「直積」と「直和」で組み立てる型
  • 不正な状態を「存在させない」型定義ができる
  • 直和型はケースごとに異なる構造を持てるので強力
  • UIの状態管理もドメインルールも同じ考え方で守れる
  • さらなる発展として、ジェネリクス(PHP 8.6)や型理論(述語や値でさらに集合を絞り込む世界)にも注目できる