42
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScript の Promise は モナドではない

Last updated at Posted at 2018-08-03

誰かの投稿にコメントしたけど、散逸するのももったいないので記事にした。

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 版はこうなる

monad-laws.js
// 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 はモナドだよね、という主張をする人は、以下の想定をしている人が多い。

promise-monad-mapping.js
const _return = x => Promise.resolve(x)
const _bind = (f, p) => p.then(f)

このようにマッピングすると確かにPromiseはモナド則を満たすケースが多いのだけれど、反例も山ほどある。

LeftIdentityが成立しない反例.js
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 で以下のように書ける。

functor-law.js
// a はファンクター
'Identity'; _map(a, x => x)  a
'Composition'; _map(a, x => f(g(x)))  _map(_map(a, f), g)

Promise が Functor であるという人は以下を念頭に入れているだろう。

promise-functor-mapping.js
const _map = (p, f) => p.then(f);

反例はすぐに上がる。

Compositionが成立しない反例.js
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 である(考えてみてほしい)

array-functor-mapping.js
const _map = (arr, f) => arr.map(f)

まとめ

Promise は Monad でも Functor でもない

参考

  • Fantasy-land: JS で関数型プログラミングする人は必見。functor則, monad則 などなどを JS のsyntax で規定
  • No, Promise is not a monad: 他にもあるだろうけど本稿と同じ趣旨のblog記事
42
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?