モナド

値をコンテキスト (失敗、非同期、リスト) で包み、連鎖的に処理する関数型プログラミングの抽象

関数型プログラミング型システム
モナド」の技術書を見る →

モナドとは

モナド (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

thenflatMap に相当する。値を取り出し、新しい Promise を返す関数を適用する。ただし then は、渡した関数が Promise を返しても素の値を返しても受け取れるため、flatMapmap を兼ねた形になっている。

モナドの 3 つの要素

モナドの 3 つの要素を以下に示す。

要素説明Promise での例
型コンストラクタ値をコンテキストで包むPromise<T>
of (unit/return)値をモナドに入れるPromise.resolve(42)
flatMap (bind/then)モナド内の値に関数を適用.then(x => ...)

この 3 つが揃っていれば必ずモナドになる、というわけではない。左単位則 (of(x)f を flatMap した結果が f(x) と等しい)、右単位則 (mof を 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 に限らず OptionControlFlow にも同じ ? が使える。なお Try トレイト自体は 2026 年 8 月時点で unstable であり、自作の型に実装して ? を使えるようにすることはできない。

なぜモナドが難しいと言われるか

モナド自体は「flatMap を持つコンテナ」というシンプルな概念だが、もともとは圏論の用語であり、その語彙のまま説明されることが多いために不必要に難解に見える。プログラミングの側では Philip Wadler の論文 Comprehending monads (Mathematical Structures in Computer Science、1992 年) などを通じて関数型言語の語彙になった。Promise の連鎖で同じ形をすでに書いているので、そこから逆算して理解するのが早い。

TypeScript での実用

TypeScript では明示的にモナドを実装する必要はほとんどない。PromisethenArray.prototype.flatMapneverthrowResult が持つ andThen が、いずれも「包まれた値を取り出して次の処理につなぐ」という同じ役割を果たす。

理論と実装の両面から学ぶなら関連書籍が参考になる。

この記事は役に立ちましたか?

関連用語

関連する記事