誰かの投稿にコメントしたけど、散逸するのももったいないので記事にした。
TL;DR;
JavaScript の Promise は モナド則を満たさないのでモナドではない
だから何?
Promise がモナドではなくても、別に困らない
関数型プログラミング指向の人で、モナドの恩恵を受けるような操作(例: 関数の lift )が必要な場合、サードパーティ製の非同期モナドを使わないといけない(例: Folktale)
モナド則を JavaScript で
モナド則を Haskell wiki から抜粋
Left identity: return a >>= f ≡ f a
Right identity: m >>= return ≡ m
Associativity: (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
JavaScript 用に書き直す。return は JavaScript では予約語なので _return
としよう。演算子 >>= もJavaScript では定義されていないので _bind
としよう。するとモナド則の JavaScript 版はこうなる
// a は任意の型の定数, m はモナド
// f は b を受け取り モナド c を返す関数(b, c は任意の型)
// g は c を受け取り モナド d を返す関数(d は任意の型)
'Left identity:'; _bind(f, _return(a)) ≡ f(a)
'Right identity:'; _bind(_return, m) ≡ m
'Associativity:'; _bind(g, _bind(f, m)) ≡ _bind(x => _bind(g, f(x)), m)
モナド則を満たさない Promise
Promise はモナドだよね、という主張をする人は、以下の想定をしている人が多い。
const _return = x => Promise.resolve(x)
const _bind = (f, p) => p.then(f)
このようにマッピングすると確かにPromiseはモナド則を満たすケースが多いのだけれど、反例も山ほどある。
const a = Promise.resolve(1) // aは任意の型なので Promise Int にしてみる
const f = x => x.then(y => y) // 上記のように設定したので xはPromise。よってthenを持つ
_bind(f, _return(a)) // -> 左辺は type error
f(a) // -> 右辺は 1 でresolve される
上記の例では、_bind の実装であるところの then が a の型である Promise を引きはがしてしまったためにモナド則が成立しなかった。Promiseの入れ子をまとめて外すという JavaScript の仕様が原因であるといえる。
これは些細なコーナーケースか?いやそんなことはなくて、JavaScript では比較的よく起きるシチュエーションだ。Promise の入れ子をはずされて、Promise に詰めなおした、という経験をもつ人は多いだろう。
Functor ですらない Promise
ついでなので、Promise が Functor ですらないことも示しておこう(察しがいい人にはもう自明だろう)。ファンクター則は JavaScript で以下のように書ける。
// a はファンクター
'Identity'; _map(a, x => x) ≡ a
'Composition'; _map(a, x => f(g(x))) ≡ _map(_map(a, f), g)
Promise が Functor であるという人は以下を念頭に入れているだろう。
const _map = (p, f) => p.then(f);
反例はすぐに上がる。
const a = Promise.resolve(1);
const f = x => x.then(y => y + 1);
const g = x => x.then(y => y * 2);
_map(a, x => f(g(x))) // 左辺: type error
_map(_map(a, f), g) // 右辺: type error
一方、Array は Functor である(考えてみてほしい)
const _map = (arr, f) => arr.map(f)
まとめ
Promise は Monad でも Functor でもない
参考
- Fantasy-land: JS で関数型プログラミングする人は必見。functor則, monad則 などなどを JS のsyntax で規定
- No, Promise is not a monad: 他にもあるだろうけど本稿と同じ趣旨のblog記事