コンテンツにスキップ

代数的データ型とパターンマッチングはOOPクラスより強力

概要

OOP の public / private などのアクセス制御は「粗悪なオモチャ」であり、本物のプログラマは代数的データ型(ADT)とパターンマッチングを使う、という主張。元ポストは @cubbit2 による「代数的データ型のおかげで、オブジェクト指向のクラスなんて粗雑なオモチャだと気付くことができた」。

詳細

代数的データ型(ADT)とは

代数的データ型は「直積型」と「直和型」の組み合わせで状態を表現する方法。

// Go の例(直和型のシミュレーション)
type Shape interface {
    area() float64
}

type Circle struct{ radius float64 }
type Rectangle struct{ width, height float64 }

func (c Circle) area() float64    { return math.Pi * c.radius * c.radius }
func (r Rectangle) area() float64 { return r.width * r.height }

Rust/Haskell のような言語では直和型がネイティブに使える:

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,
    }
}

OOP クラス vs ADT の比較

観点 OOP クラス 代数的データ型
状態管理 カプセル化で隠蔽 型で明示的に表現
網羅性チェック なし(実行時エラー) コンパイル時に全ケース強制
副作用 メソッドに混在しやすい 純粋関数と分離しやすい
拡張性 継承/インターフェース パターンマッチで追加

アクセス修飾子の限界

private は「外から触るな」という制約だが、クラス内部の不変条件を型で保証しない。ADT では型自体が取り得る状態を制限するため、不正な状態をコンパイル時に排除できる。

なぜ重要か / いつ使うか

  • エラー処理:Result<T, E>Option<T> のような型で null/例外を型安全に扱う
  • 状態機械:有限状態の遷移を型で表現し、不正な遷移をコンパイルエラーにする
  • Go では直和型は弱いが、errors.As や tagged union パターンで近い表現が可能
  • TypeScript では type Result = Success | Failure のような discriminated union が使える