モナド
値をコンテキスト (失敗、非同期、リスト) で包み、連鎖的に処理する関数型プログラミングの抽象
モナドとは
モナド (Monad) は、値をコンテキスト (失敗の可能性、非同期、複数の値) で包み、flatMap (bind) で連鎖的に処理する関数型プログラミングの抽象である。Promise、Option、Result、Array が身近な例だが、後述のモナド則を厳密に満たすかどうかは型ごとに違う。
日常のモナド: Promise
日常のモナド: Promise のコード例を示す。
// Promise は「非同期」というコンテキストで値を包む
const result = await Promise.resolve(1) // Promise<number>
.then(x => Promise.resolve(x + 1)) // flatMap: number → Promise<number>
.then(x => Promise.resolve(x * 2)); // flatMap: number → Promise<number>
// result: 4
then は flatMap に相当する。値を取り出し、新しい Promise を返す関数を適用する。ただし then は、渡した関数が Promise を返しても素の値を返しても受け取れるため、flatMap と map を兼ねた形になっている。
モナドの 3 つの要素
モナドの 3 つの要素を以下に示す。
| 要素 | 説明 | Promise での例 |
|---|---|---|
| 型コンストラクタ | 値をコンテキストで包む | Promise<T> |
of (unit/return) | 値をモナドに入れる | Promise.resolve(42) |
flatMap (bind/then) | モナド内の値に関数を適用 | .then(x => ...) |
この 3 つが揃っていれば必ずモナドになる、というわけではない。左単位則 (of(x) に f を flatMap した結果が f(x) と等しい)、右単位則 (m に of を flatMap すると m に戻る)、結合律 (連鎖のまとめ方を変えても結果が変わらない) の 3 つの法則を満たすことが条件になる。
Promise はこのうち左単位則を満たさない。Promise.resolve は渡された値がすでに Promise ならそれ自身を返し、then は入れ子になった Promise を 1 段に平坦化する。そのため包みたい値そのものが Promise であるケースで、of(x) に f を flatMap した結果と f(x) が食い違う。Promise がモナドの例として紹介されるのは理解の足がかりとしてであり、厳密にはモナドの条件を外れている。
身近なモナド
身近なモナドを以下にまとめる。
| モナド | コンテキスト | flatMap に当たる操作 |
|---|---|---|
Promise<T> | 非同期 | .then() (JavaScript) |
Option<T> | 値の有無 | and_then (Rust)。TypeScript には標準の型が無いので自作する |
Result<T, E> | 成功/失敗 | and_then (Rust)、andThen (neverthrow) |
Array<T> | 複数の値 | .flatMap() (JavaScript) |
Option モナドの連鎖
Option モナドの連鎖のコード例を示す。
type Option<T> = { kind: 'some'; value: T } | { kind: 'none' };
function flatMap<T, U>(opt: Option<T>, fn: (v: T) => Option<U>): Option<U> {
return opt.kind === 'some' ? fn(opt.value) : { kind: 'none' };
}
// null チェックの連鎖をモナドで解決
const result = flatMap(findUser('123'), user =>
flatMap(findOrder(user.orderId), order =>
findProduct(order.productId)
)
);
// どこかで none が返ったら、以降の処理はスキップされる
Rust の ? 演算子
Rust の ? 演算子のコード例を示す。
// ? は Err をそのまま呼び出し元へ伝播させる
fn get_product_name(user_id: &str) -> Result<String, Error> {
let user = find_user(user_id)?; // Err なら早期リターン
let order = find_order(&user.order_id)?;
let product = find_product(&order.product_id)?;
Ok(product.name)
}
効果は flatMap の連鎖に似ているが、機序は別物である。Rust リファレンスはこの構文を try 伝播式と呼び、Try トレイトを使って「内側の式から値を取り出すか、その場で呼び出し元へ return するか」を決めるものと規定している。次につなぐ関数を渡すのではなく制御を返す仕組みなので、Result に限らず Option や ControlFlow にも同じ ? が使える。なお Try トレイト自体は 2026 年 8 月時点で unstable であり、自作の型に実装して ? を使えるようにすることはできない。
なぜモナドが難しいと言われるか
モナド自体は「flatMap を持つコンテナ」というシンプルな概念だが、もともとは圏論の用語であり、その語彙のまま説明されることが多いために不必要に難解に見える。プログラミングの側では Philip Wadler の論文 Comprehending monads (Mathematical Structures in Computer Science、1992 年) などを通じて関数型言語の語彙になった。Promise の連鎖で同じ形をすでに書いているので、そこから逆算して理解するのが早い。
TypeScript での実用
TypeScript では明示的にモナドを実装する必要はほとんどない。Promise の then、Array.prototype.flatMap、neverthrow の Result が持つ andThen が、いずれも「包まれた値を取り出して次の処理につなぐ」という同じ役割を果たす。
理論と実装の両面から学ぶなら関連書籍が参考になる。
この記事は役に立ちましたか?
関連用語
関連する記事
「動くコード」と「良いコード」の間にある本
コードが動くようになった後、次に何を学べばよいのか。「動くコード」を「良いコード」に変えるために必要な知識と、それを効率的に学べる本の選び方を解説します。
Pull Request が通らない人に足りないのは、たいてい語彙力
コードレビューで指摘が多い人に共通する「設計の語彙力不足」という問題と、技術書で語彙力を効率的に増やす方法を解説します。
ペアプロ相手が本を読んでいると会話の密度が変わる
ペアプログラミングやモブプログラミングで、相手が技術書を読んでいるかどうかで議論の質が変わります。共通の語彙がある場合とない場合の差と、チームの読書文化が開発効率に与える影響を考えます。