モナド:「箱」というデザインパターン
先に結論を言っておくので安心してください。モナドは高尚な数学オブジェクトではなく、素朴なプログラミングパターン――「値を箱に入れて、窓からしか取り出せないと決める」――にすぎません。 あなたはとっくに使いこなしています。ただ、その名前を知らなかっただけです。
ステップ 1:なぜ「箱」が必要なのか?
まず、あなたが毎日直面している悩み――null チェック地獄を見てみましょう:
function 查朋友(id) {
const user = users[id];
if (user === undefined) return '查无此人'; // ① null チェック
const friend = friends[user];
if (friend === undefined) return '这人没朋友'; // ② また null チェック
return friend;
}
「失敗するかもしれない」操作を 1 つ追加するたびに、if を 1 つ追加しなければなりません。コードが長くなるほど null チェックが増え、1 つ書き忘れたら爆発します。箱(モナド)は、これらの if を消し去るためにあるのです。
ステップ 2:最も単純な箱を作る――Maybe ボックス
この箱には 2 つの状態しかありません。値が入っているか、空かです。
function Just(value) { return { type: 'just', value }; } // 値あり
function Nothing() { return { type: 'nothing' }; } // 空
たったこれだけです――オブジェクト 1 つにタグを 1 つ付けただけ。何の神秘もありません。
ステップ 3:箱のルール――値を直接取り出してはいけない
箱の最も重要な設計:中の値を直接取り出すことはできません。中の値を使いたければ、「窓」を通すしかありません:
// 窓 1:map ―― 箱に値があれば f で処理して箱に戻す。なければそのまま流す
function map(box, f) {
if (box.type === 'nothing') return Nothing();
return Just(f(box.value));
}
// 窓 2:flatMap ―― モナドの魂!f は「新しい箱」を返す
function flatMap(box, f) {
if (box.type === 'nothing') return Nothing();
return f(box.value); // f が箱を返す → そのまま返す → 自然に「平坦化」される
}
map と flatMap の中にある null チェックの行に注目してください――null チェックのロジックが窓の中に書き込まれているので、あなたはもう null チェックを手書きする必要がありません!
概念の対応:
Just(x)は論文でいう η (eta) ――「純粋な値を箱に入れる」操作です。flatMapは論文でいう μ (mu) と bind ――「箱の中の箱を平坦化する」操作です。
ステップ 4:これで null チェック地獄を解決する
「ユーザー検索 → 友達検索」の場面に戻りましょう。今度は各ステップが箱を返します:
function findUser(id) { return users[id] ? Just(users[id]) : Nothing(); }
function findFriend(u) { return friends[u] ? Just(friends[u]) : Nothing(); }
// チェーン呼び出し:if はゼロ!
function getFriendOf(id) {
return flatMap(findUser(id), findFriend);
}
実行結果:
| 呼び出し | 結果 | 意味 |
|---|---|---|
getFriendOf(1) | Just('小刚') | 正常に見つかった |
getFriendOf(999) | Nothing | ユーザーが見つからない。空が自動的に下流へ伝わり、クラッシュしない |
getFriendOf(2) | Just('小丽') | 正常に見つかった |
何がすごいのか? ユーザーが存在しない場合、findUser は空の箱を返します。flatMap は空だと見るや findFriend を呼ばずに、空をそのまま下流へ流します。null チェックのロジックは箱が自動的に引き受けてくれる――書き忘れようとしても忘れられません。しかも何層でも連鎖させられ、コードの構造はいつも同じです:
const deep = flatMap(flatMap(findUser(1), findFriend), findFriend2);
// → Just('大壮')
ステップ 5:サプライズ発表――あなたは毎日モナドを使っている。それは Promise と呼ばれる
| 箱(Maybe) | Promise | どちらも「箱」 |
|---|---|---|
Just(5) | Promise.resolve(5) | 普通の値を箱に入れる |
map(box, f) | promise.then(f) | 箱の中の値を変換する |
flatMap(box, f) | promise.then(f)(f は Promise を返す) | 箱の中に箱、自動的に平坦化 |
| 空の箱が下流へ伝わる | rejected な Promise が下流へ伝わる | 失敗が自動的に伝播し、クラッシュしない |
box.value(禁止) | await(言語レベルでのみ許可) | 箱を開ける唯一の方法 |
唯一の違いは、箱に「付随するもの」が異なることだけです:
- Maybe ボックスに付随するもの:「この値は存在しないかもしれない」
- Promise ボックスに付随するもの:「この値は少し待つ必要がある、そして失敗するかもしれない」
箱そのもの(デザインパターン)はまったく同じです。 だからこそ「.then チェーンが書けるなら、あなたはすでにモナドを使っている」と言われるのです。
η 装值、μ 压平——盒子的规矩让副作用显式、可控、可组合
モナドとこの論文の関係は?
一言でいえば:モナドは「関数型言語で副作用を扱う」ための標準ツールです――副作用を箱に入れることで、副作用は明示的で、制御可能で、合成可能になります。
そしてこの論文の野望は、「副作用」を「コンパイル時の型システムが管理する静的な概念」から「実行時に本当に取り消せる動的な仕組み」へと格上げすることです(覚えていますか? ロールバック可能な作用)。つまり論文がモナドに言及するのは、「作用」という概念に理論上の出自を与えているだけにすぎません――モナド則を導出できる必要はありません。「モナド = 副作用を入れる箱」だと知っていれば十分です。
💡 試してみよう:リポジトリのルートにある
monad-demo.jsは実行可能なデモコードです。node monad-demo.jsを実行すれば、上記のすべての例が実際に動く結果を見られます。
セルフチェック · モナド
回答を終えたら「解答を送信」をクリックすると、正誤と解説を確認できます。
