モナド

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

関数型プログラミング型システム

モナドとは

モナド (Monad) は、値をコンテキスト (失敗の可能性、非同期、複数の値) で包み、flatMap (bind) で連鎖的に処理する関数型プログラミングの抽象である。Promise、Option、Result、Array が身近なモナドだ。

日常のモナド: 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 を返す関数を適用する。

モナドの 3 つの要素

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

身近なモナド

モナド コンテキスト flatMap
Promise<T> 非同期 .then()
Option<T> 値の有無 .flatMap()
Result<T, E> 成功/失敗 .flatMap()
Array<T> 複数の値 .flatMap()

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 の ? はモナドの flatMap の糖衣構文
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 を持つコンテナ」というシンプルな概念だが、圏論 (Category Theory) の用語で説明されることが多く、不必要に難解に見える。Promise を使ったことがあれば、既にモナドを使っている。

TypeScript での実用

TypeScript では明示的にモナドを実装する必要はほとんどない。PromiseArray.flatMapneverthrow の Result 型が実質的にモナドとして機能する。

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

関連用語