モナド
値をコンテキスト (失敗、非同期、リスト) で包み、連鎖的に処理する関数型プログラミングの抽象
関数型プログラミング型システム
モナドとは
モナド (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
then は flatMap に相当する。値を取り出し、新しい 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 では明示的にモナドを実装する必要はほとんどない。Promise、Array.flatMap、neverthrow の Result 型が実質的にモナドとして機能する。
理論と実装の両面から学ぶなら関連書籍が参考になる。