スポンサーLobeHubLobeHub詳しく見る
dshfind

モナド:「箱」というデザインパターン

先に結論を言っておくので安心してください。モナドは高尚な数学オブジェクトではなく、素朴なプログラミングパターン――「値を箱に入れて、窓からしか取り出せないと決める」――にすぎません。 あなたはとっくに使いこなしています。ただ、その名前を知らなかっただけです。

ステップ 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 が箱を返す → そのまま返す → 自然に「平坦化」される
}

mapflatMap の中にある 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 チェーンが書けるなら、あなたはすでにモナドを使っている」と言われるのです。

(可能为空 / 可能还没到)Just(x) / resolvemap / flatMap在盒子里操作规则:不能直接把值抠出来Promise 就是这种盒子

η 装值、μ 压平——盒子的规矩让副作用显式、可控、可组合

モナドとこの論文の関係は?

一言でいえば:モナドは「関数型言語で副作用を扱う」ための標準ツールです――副作用を箱に入れることで、副作用は明示的で、制御可能で、合成可能になります。

そしてこの論文の野望は、「副作用」を「コンパイル時の型システムが管理する静的な概念」から「実行時に本当に取り消せる動的な仕組み」へと格上げすることです(覚えていますか? ロールバック可能な作用)。つまり論文がモナドに言及するのは、「作用」という概念に理論上の出自を与えているだけにすぎません――モナド則を導出できる必要はありません。「モナド = 副作用を入れる箱」だと知っていれば十分です。

💡 試してみよう:リポジトリのルートにある monad-demo.js は実行可能なデモコードです。node monad-demo.js を実行すれば、上記のすべての例が実際に動く結果を見られます。

セルフチェック · モナド

回答を終えたら「解答を送信」をクリックすると、正誤と解説を確認できます。

1. モナド(Monad)の最も素朴な理解はどれですか?
2. Maybe ボックス(Just / Nothing)が解決する中心的な課題は何ですか?
3. 箱のモデルで、Promise.resolve(x) は何に対応しますか?
4. 論文がモナドを取り上げた主な目的は何ですか?