2022/1/13 型コンストラクタを「抽象型」としていたのを「多相型」に修正しました
最近になって、プログラミングにおけるモナドには「モナド則」という規則があることを知り、これを使えばこれまた最近聞いた「JavaScriptのPromiseはモナドである」というのが本当かどうか確かめられるのでは?と思ったので確かめてみた。
#JavaScript の #Promise は #モナド である、という話を聞いたのだが、それはマジなんでゲスか?#プログラミング
— Ukicode (@opaupafz2) 2021年12月24日
注意事項
筆者はモナド初心者であるため、この記事を読んで「納得できる」ないし「理解できる」とは限らないことをご了承いただければ幸いである(マサカリを投げたりしていただいても構いません)。
モナド則って何よ?
モナド則とは対象がモナドであるために成り立っていなければならない規則である。つまりこのモナド則が成り立たない対象はモナドではないことを示し、裏を返せば、モナド則が成り立つ対象はモナドとなる。
モナドの形式的定義
モナド則について説明する前に、モナドの形式的な定義について説明せねばなるまい。モナドの形式的定義は以下の3つから成る。
要素 | 型(カインド) | 概要 |
---|---|---|
型コンストラクタM |
* -> * |
モナドとなる多相型*1 |
return 関数*2*3 |
a -> M a |
型a の値を型M a の値に変換する関数*4 |
(>>=) 演算子*5 |
M a -> (a -> M b) -> M b |
型M a の値から型a の値を抽出し、型M b の値に変換する関数を適用する関数 |
つまり、モナドである型には型コンストラクタM
、return
関数、(>>=)
演算子の3つもしくはそれらに類似する機能がほぼ確実にあると言っても過言ではない。
3つのモナド則
そしてモナド則の話に戻るが、モナドの形式的定義があるからと言って、それがモナドであるとは限らない。モナドであるためには、定式的定義だけでなく、モナド則が成り立ってなければならない。
モナド則は、以下の3つのとおりである。
- モナド則1:
return(x) >>= f ≡ f(x)
「return
関数にx
を適用し得たモナドから値を抽出し『値からモナドを返す』関数f
に適用する」ことは「f
に直接x
を適用する」ことと同じでなければならない
- モナド則2:
m >>= return ≡ m
モナドm
から値を抽出しreturn
関数に適用した結果はm
と同じでなければならない
- モナド則3:
(m >>= f) >>= g ≡ m >>= (λx. (f(x) >>= g))
「モナドm
から関数f
→関数g
の順に連続で値を抽出し適用していく」ことは「モナドm
から値を抽出し、『f
に適用した結果から値を抽出しg
に適用する』関数に適用すること」と同じでなければならない。
以上が、自分なりに解釈した「モナド則」である。
JavaScriptのPromiseがモナドであるかどうか確認
では早速JavaScriptのPromiseがモナドであるかどうか、モナド則から確認してみる。JavaScriptのPromiseについての詳細は、以下を参照されたし。
developer.mozilla.org
まず、Promiseの何がモナドの形式的定義のうちどれに当てはまっているのかについては、以下がそれではないかと考えられる。
要素 | 該当するPromiseの機能 | 解説 |
---|---|---|
M |
Promise |
言わずもがな |
return |
Promise.resolve など |
値を適用した結果のPromiseを返す |
(>>=) |
Promise.prototype.then など |
結果から値を適用した結果のPromiseを返す |
つまり実際にPromiseがモナドであると言うならば、
printLn = λx. 標準出力関数(x) return(1) >>= printLn
は、JavaScriptのPromiseで言う
const consoleLog = x => Promise.resolve(console.log(x));
Promise.resolve(1).then(consoleLog);
と一緒である、という前提で話を進める。
モナド則1
モナド則1はreturn(x) >>= f ≡ f(x)
である。つまりJavaScriptのPromise的には、Promise.resolve(x).then(f) ≡ f(x)
となるはずである*6。
const f = x => Promise.resolve(console.log(String(x))); Promise.resolve(42).then(f);
"42"
const f = x => Promise.resolve(console.log(String(x))); f(42);
"42"
結果がまったく同じだったため、モナド則1は満たしていることが確認できた。
モナド則2
モナド則2はm >>= return ≡ m
である。つまりJavaScriptのPromise的には、promise.then(x => Promise.resolve(x)) ≡ promise
となるはずである*7。
const printPromise = p => p.then(n => console.log(n)); const promise = Promise.resolve(42); // Promiseの出力だけでは同じ結果なのか判断できないため、値を出力する printPromise(promise.then(x => Promise.resolve(x)));
42
const printPromise = p => p.then(n => console.log(n)); // Promiseの出力だけでは同じ結果なのか判断できないため、値を出力する printPromise(Promise.resolve(42));
42
結果がまったく同じだったため、モナド則2も満たしていることが確認できた。
モナド則3
モナド則3は(m >>= f) >>= g ≡ m >>= (λx. (f(x) >>= g))
である。つまりJavaScriptのPromise的には、(promise.then(f)).then(g) ≡ promise.then(x => (f(x).then(g)))
となるはずである*8。
const f = x => Promise.resolve([x]); const g = x => Promise.resolve(console.log(x)); const promise = Promise.resolve(42); (promise.then(f)).then(g);
Array [42]
const f = x => Promise.resolve([x]); const g = x => Promise.resolve(console.log(x)); const promise = Promise.resolve(42); promise.then(x => (f(x).then(g)));
Array [42]
結果がまったく同じだったため、モナド則3も満たしていることが確認できた。
結論
以上の結果から、JavaScriptのPromiseでモナド則が成り立つ*9ことがわかったので、「JavaScriptのPromiseはモナドである」という情報が自分の中でより確実なものとなった。