モナド
#functional-programming #haskell
値を文脈(コンテキスト)付きで包み、その文脈を保ったまま計算を繋げていくための仕組み。関数型プログラミング(特にHaskell)で、失敗しうる計算・状態・入出力など「純粋な関数だけでは表現しにくいもの」を統一的に扱うための型クラス/デザインパターン。Currying(カリー化)と並んで関数型プログラミングの基本語彙のひとつ。
一言でまとめると「包んだ値に対する計算の連結方法を、型ごとに定義できる共通インターフェース」。
構成要素
モナド M は2つの操作を持つ。
- return (unit) — 普通の値
aをモナドの値M aに包む。例:return 5→Just 5 - bind (
>>=) —M aと「a → M bな関数」を受け取り、包みを開けて関数を適用し、結果をまたM bとして返す。これで「文脈付き計算の連結」ができる
モナド則
正しいモナドは3つの法則を満たす必要がある。Haskellコンパイラは検査しないので、インスタンスを書くプログラマの責任。
- 左単位元:
return x >>= f≡f x - 右単位元:
m >>= return≡m - 結合律:
(m >>= f) >>= g≡(\x -> f x >>= g)にmを bind したものと同じ
この3つが成り立つおかげで、do記法で書いた逐次的なコードが直感通りに動くことが保証される。
代表的なモナド
| モナド | 文脈 | 何が嬉しいか |
|---|---|---|
Maybe |
失敗するかもしれない | Nothing が出たら以降を自動スキップ。nullチェックの連鎖が消える |
Either |
エラー情報付きの失敗 | エラーを値として伝搬 |
List |
非決定性(複数の結果) | 全組み合わせの計算が自然に書ける |
IO |
副作用 | 純粋な言語の中に入出力を閉じ込める |
State |
状態の引き回し | 状態渡しの定型コードを隠蔽 |
Maybe の例。bindが Nothing を自動で伝搬してくれるので、手書きなら「結果を確認して、Nothingでなければ次へ…」となるif連鎖が消える。
lookupUser name >>= lookupAddress >>= lookupZipCode
-- どこかで Nothing になれば、最終結果も Nothing
JavaScriptの Promise.then やRustの Option/Result の and_then も、実質的に同じ構造(bind相当)を持っている。
共通インターフェースだと何が嬉しいのか
モナドの価値は個々の Maybe や IO そのものではなく、それらを貫く共通の「形」に名前を付けた点にある。形に対する道具(汎用関数・構文・リファクタリング則)を一度作れば全部の型で使い回せる。
1. 定型処理(配管コード)がロジックから消える
「失敗チェックして次へ」「状態を次の関数に手渡し」「エラーを上に伝搬」といった処理は、本来やりたい計算とは無関係の"配管"。これを bind の定義に一度だけ押し込むと、利用側のコードにはビジネスロジックだけが残る。
2. 汎用関数が「無料で」ついてくる
bind と return だけを使って書かれた関数は、すべてのモナドに対して自動で動く。Haskellの Control.Monad には mapM/forM(for-eachループ相当)、sequence([M a] → M [a])、replicateM、when、foldM などが揃っていて、自分の型に Monad インスタンスを定義した瞬間、これら全部が使えるようになる。Iterator インターフェースを実装すれば map/filter が全部使えるのと同型の嬉しさ。
3. 言語側の構文サポートが1つで済む
Haskellの do 記法は「モナドという単一のインターフェース」への糖衣構文なので、言語は構文を1つ用意するだけで、失敗処理にも状態にも非同期にも同じ書き味を提供できる。共通化しない場合、JavaScriptが Promise 専用に async/await を、Rustが Result 専用に ? を用意したように、文脈の種類ごとに専用構文を言語に足していくことになる。
4. 文脈の差し替えがロジックの書き換えなしにできる
ロジックがインターフェース越しに書かれていれば、「Maybe を Either に変えたい」「後からログ蓄積を足したい」というとき、モナドの定義側を差し替えるだけでロジック側はそのまま動く。モナド則が保証されているおかげで、差し替えやリファクタリングをしても挙動の予測が崩れない。
圏論との関係
「モナドは自己関手の圏におけるモノイド対象」という圏論由来の定義もあるが、プログラミングで使う分には上記の「共通インターフェース」理解で十分。