はじめに
「代数的データ型(ADT)」という言葉を聞くと、なんだか数学の難しい理論のように感じて身構えてしまいませんか?
しかし、実態は私たちが TypeScript で日常的に使っている Union型(共用体型) と Intersection型(交差型) 、そしてそれらを組み合わせたパターンの延長線上にあります。
基本情報等やITの基礎で習う積集合と和集合をイメージするとわかりやすいかも知れないです。

画像はIT用語時点より
代数的データ型とは
代数的データ型とは、簡単に言うと「複数の型を組み合わせて作られる新しい型」のことです。主に以下の2つの考え方に基づいています。
- 直和型(Sum Type): 「A または B」という状態を表すもの。(TypeScript の Union 型)
- 直積型(Product Type): 「A と B の組み合わせ」という状態を表すもの。(TypeScript の オブジェクトやタプル)
「代数的」と呼ばれる理由は、これらの組み合わせが集合論的な「足し算(和)」や「掛け算(積)」のように振る舞うからです。
特に TypeScript において強力なのが、「タグ付きユニオン(Tagged Union)」 というパターンです。これを利用することで、データの状態に応じた安全な条件分岐(パターンマッチング的な処理)が実現できます。
サンプルコード
例えば、APIのレスポンス状態を管理する場合を考えてみましょう。「データはあるけど読み込み中」といった矛盾した状態を防ぐために、代数的データ型が役立ちます。
// 1. 各状態を定義(直積型の定義)
type Loading = {
status: "loading";
};
type Success<T> = {
status: "success";
data: T;
};
type Failure = {
status: "failure";
error: Error;
};
// 2. 直和型(代数的データ型)として統合
type RemoteData<T> = Loading | Success<T> | Failure;
// 3. 実際に使用
const fetchData = (state: RemoteData<string>) => {
// statusによって型ガードが効く
switch (state.status) {
case "loading":
return "読み込み中...";
case "success":
return `データ受取完了: ${state.data}`;
case "failure":
return `エラー発生: ${state.error.message}`;
}
};
const myData: RemoteData<string> = { status: "success", data: "Hello World" };
console.log(fetchData(myData));
おわりに
代数的データ型を意識すると、if 文や null チェックの羅列を減らし、ドメインモデルをより正確に型に落とし込むことができます。
実際に開発している際も意識してみるとより良いコードが書けるかもしれません。
Ref