双対
2つの概念が「矢印を逆にしただけ」の関係にあること。
プログラマ向けの一言
AND と OR は双対。push と pull は双対。Producer と Consumer は双対。 片方を理解すれば、矢印を逆にするだけでもう片方が自動的に得られる。
コードで理解する
// --- 直積と直和は双対 ---
// 直積 (Product): A AND B — 両方持つ
type Product = { a: string; b: number };
// ← 作るとき: a と b の両方が必要
// ← 使うとき: a だけ取り出す or b だけ取り出す(選ぶ)
// 直和 (Sum / Coproduct): A OR B — どちらか
type Sum = { kind: "a"; value: string } | { kind: "b"; value: number };
// ← 作るとき: a か b のどちらかを選ぶ
// ← 使うとき: 両方のケースを処理する必要がある
// Product: 作るのは AND、使うのは OR
// Sum: 作るのは OR、使うのは AND
// → 矢印(作る/使う)を逆にすると入れ替わる = 双対
// --- Iterator と Observer は双対 ---
// Iterator (pull): 消費者がデータを引っ張る
interface Iterator<T> {
next(): { value: T; done: boolean };
}
// ← Consumer が「次をくれ」と要求する
// Observer (push): 生産者がデータを押し込む
interface Observer<T> {
next(value: T): void;
complete(): void;
}
// ← Producer が「これ受け取って」と通知する
// データの流れる方向が逆なだけで、構造は同じ
// for ループ ←→ イベントリスナ
// Array.map ←→ Observable.map (RxJS)
// --- async/await と Generator は双対 ---
// Generator (pull): 呼び出し側が .next() で値を引っ張る
function* producer() { yield 1; yield 2; yield 3; }
// async iteration (push): 値が来るのを待つ
async function consumer(stream: AsyncIterable<number>) {
for await (const value of stream) { console.log(value); }
}
なぜ有用か
// 双対性を知っていると:
// 1. 片方の概念を理解すれば、もう片方を自動的に導出できる
// 2. 片方の定理を証明すれば、双対の定理もタダで得られる
// 3. API 設計で対称性を見つけて、一貫したインターフェースを作れる
// 例: RxJS は Iterator の双対として Observable を体系的に設計した
// map, filter, reduce が Iterator にあるなら
// Observable にも map, filter, reduce があるべき → 実際にある
押さえどころ(カード化候補)
- 双対とは → 2 つの概念が「矢印を逆にしただけ」の関係。片方を理解すれば逆向きでもう片方が自動的に得られる
- 直積と直和 → Product(A AND B)は作るのが AND・使うのが OR、Sum(A OR B)は作るのが OR・使うのが AND。作る/使うの矢印を逆にすると入れ替わる
- Iterator と Observer → Iterator は pull(消費者が next で引く)、Observer は push(生産者が next で押し込む)。データの流れる向きが逆なだけで構造は同じ(for ループ ⟷ イベントリスナ)
- Generator と async iteration → Generator は pull、
for awaitは push。同じ map/filter/reduce 群が両側に対称的に存在(RxJS は Iterator の双対として体系設計) - なぜ有用か → 片方の概念・定理から逆をタダで導出でき、API 設計で対称性・一貫性を作れる