私は関数型プログラミングについて学び、モナド、ファンクター、およびアプリカティブに出会いました。
私の理解から、次の定義が適用されます。
a)(A => B)=> C [A] => C [B] |ファンクタ
b)(A => C [B])=> C [A] => C [B] |モナド
c)(C [A => B])=> C [A] => C [B] |適用する
(参照: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/ )
さらに、モナドはファンクターの特殊なケースであると理解しています。同様に、関数ラップされた値を返すをラップされた値に適用し、ラップされた値を返します。
Promise.then(func)
を使用する場合、通常A => B
シグネチャを持つ関数にPromise(i.e。C [A])を渡し、別のPromise(i.e. C [B])を返します。したがって、私の考えでは、func
はC [B]ではなくBを返すため、PromiseはFunctorであり、Monadではありません。
しかし、グーグルでは、PromiseはFunctorであるだけでなく、Monadでもあることがわかりました。 func
がラップされた値C [B]ではなくBを返すので、なぜだろうか。
UDATE。ファンクタとモナド演算子を証明するこの新しいライブラリを参照してください。以下に概説するように、neablesに問題のない単純なコールバックベースの関数があります。
https://github.com/dmitriz/cpsfy
組成保存法 (関数の合成を画像の合成に送信する)に違反しているため、ファンクターではありません。
_promise.then(x => g(f(x)))
_
同等ではありません
_promise.then(f).then(g)
_
これが実際的に意味することは、リファクタリングすることは決して安全ではないことです
_promise
.then(x => f(x))
.then(y => g(y))
_
に
_promise
.then(x => g(f(x))
_
Promise
はファンクターでした。
ファンクター法違反の証拠。ここに反例があります:
//ファンクター構成保存法: // promise.then(f).then(g)vs promise.then(x => g(f(x))) // fは関数 `x` //を取り、` then` propの下のオブジェクトに保存します: const f = x =>({then:x}) // gは、オブジェクト const g = obj => obj.then // h = composeから `then`プロップを返します(g、f)は恒等式です const h = x => g(f(x)) //恒等関数で約束を果たします const promise = Promise.resolve(a => a) //このpromiseは、アイデンティティ関数 promise.then(h) .then( res => { console.log( "then(h)が返します:"、res) }) // => "then(h)が返します:" a = > a //しかし、この約束は決して満たされない promise.then(f) .then(g) .then(res => { console.log( "then(f).then(g)が返します:"、res) }) // => ??? //これは promise.then(f) .then(res => { console.log( "then(f)Returns:"、res)ではないためです。 })
Codepenのこの例を次に示します。 https://codepen.io/dmitriz/pen/QrMawp?editors=0011
構成h
は恒等関数であるため、promise.then(h)
は、単にpromise
の状態を採用します。これは、アイデンティティ_a => a
_ですでに満たされています。
一方、f
は、いわゆる thenable を返します。
1.2。 「thenable」は、thenメソッドを定義するオブジェクトまたは関数です。
ファンクタの法則を守るには、_.then
_が単に結果f(x)
をpromiseにラップする必要があります。代わりに、 Promise Spec では、_.then
_内の関数が「thenable」を返す場合に異なる動作が必要です。 2.3.3. に従って、then
キーの下に格納されている識別関数_id = a => a
_は次のように呼び出されます。
_id(resolvePromise, rejectPromise)
_
ここで、resolvePromise
とrejectPromise
は、Promise解決プロシージャによって提供される2つのコールバック関数です。ただし、解決または拒否するには、これらのコールバック関数の1つを呼び出す必要があります。したがって、結果のプロミスは保留状態のままになります。
この例では、promise.then(x => g(f(x)))
は恒等関数_a => a
_で満たされますが、promise.then(f).then(g)
は永久に保留状態のままです。したがって、これらの2つの約束は同等ではなく、したがってファンクターの法律に違反します。
Pointed Functor Specからの自然な変換法則でさえ、それは Applicative (準同型法則)の一部であるため、違反されているからです:
_Promise.resolve(g(x)) is NOT equivalent to Promise.resolve(x).then(g)
_
証明ここに反例があります:
//「then」の下に保存された識別関数prop const v =({then:a => a}) // `g`は` then` prop from object const g = obj => obj.then // `g(v)`は恒等関数 Promise.resolve( g(v))。then(res => { console.log( "resolve(g(v))Returns:"、res) }) // = > "resolve(g(v))は以下を返します:" a => a // `v`は永久に保留中のpromiseにラップ解除されます //任意のコールバック Promise.resolve(v).then(g).then(res => { console.log( "resolve(v).then(g)Returns:"、 res) }) // => ???
Codepenのこの例: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011
この例でも、一方の約束が満たされているのに対し、もう一方の約束は保留中です。したがって、2つの約束はいずれの意味でも同等ではなく、法律に違反します。
更新。
PromisebeingFunctor/Applicative/Monadをそのまま使用する方法と、makeそのようなメソッドを変更するか、新しいメソッドを追加します。ただし、ファンクターにはmap
メソッド(必ずしもこの名前である必要はありません)が既に提供されている必要があり、ファンクターになることは明らかにこのメソッドの選択に依存します。法律が満たされている限り、メソッドの実際の名前は何の役割も果たしません。
Promisesでは、_.then
_が最も自然な選択であり、以下に説明するFunctorの法則に違反します。私が見る限り、他のPromiseメソッドはどれも考えられる方法でファンクターにすることはできません。
法律に適合する他の方法を定義できるかどうかは異なる問題です。私が知っているこの方向での唯一の実装は、 creed library によって提供されます。
しかし、かなりの代価を支払う必要があります:まったく新しいmap
メソッドを定義する必要があるだけでなく、Promiseオブジェクト自体にも必要です。変更される:creed
promiseは、値として "theneable"を保持できますが、ネイティブJS Promiseは保持できません。以下で説明するように、この変更は、例の法律に違反しないようにするために重要であり必要です。特に、このような根本的な変更なしに、PromiseをFunctor(またはMonad)にする方法を知りません。
Promise
は (a lot like)then
がオーバーロードされているためモナドです。
Promise.then(func)を使用する場合、Promise(i.e。C [A])に通常署名A => Bを持つ関数を渡し、別のPromise(i.e. C [B])を返します。したがって、私の考えでは、funcはCでなくBを返すため、PromiseはFunctorであり、Monadではありません。
これはthen(Promise<A>, Func<A, B>) : Promise<B>
に当てはまります(javascriptタイプの擬似コードを許せば、this
が最初の引数であるかのように関数を記述します)
promise APIはthen
に別の署名を提供しますが、then(Promise<A>, Func<A, Promise<B>>) : Promise<B>
です。このバージョンは、明らかにモナドバインド(>>=
)。自分で試してみてください、それは動作します。
ただし、モナドの署名を適合させることは、Promiseがモナドであることを意味しません。また、モナドの代数則を満たす必要があります。
モナドが満たさなければならない法則は結合性の法則です
(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) )
そして、左と右のアイデンティティの法則
(return v) >>= f ≡ f v
m >>= return ≡ m
javaScriptで:
function assertEquivalent(px, py) {
Promise.all([px, py]).then(([x, y]) => console.log(x === y));
}
var _return = x => Promise.resolve(x)
Promise.prototype.bind = Promise.prototype.then
var p = _return("foo")
var f = x => _return("bar")
var g = y => _return("baz")
assertEquivalent(
p.bind(f).bind(g),
p.bind(x => f(x).bind(g))
);
assertEquivalent(
_return("foo").bind(f),
f("foo")
);
assertEquivalent(
p.bind(x => _return(x)),
p
);
約束に精通している人なら誰でもこれらのすべてが真実であるべきだとわかると思いますが、自分で試してみてください。
promiseはモナドであるため、ap
を派生させて、そこから適用することもできます。これにより、少し賢明ではないハッカーを使用した非常に素晴らしい構文が得られます。
Promise.prototype.ap = function (px) {
return this.then(f => px.then(x => f(x)));
}
Promise.prototype.fmap = function(f) {
return this.then(x => f(x));
}
// to make things pretty and idiomatic
Function.prototype.doFmap = function(mx) {
return mx.fmap(this);
}
var h = x => y => x + y
// (h <$> return "hello" <*> return "world") >>= printLn
h.doFmap(_return("hello, ")).ap(_return("world!")).bind(console.log)
Promiseは、関数であるthenプロパティを含むオブジェクトを特別なケースとして扱います。このため、彼らは以下のように左のアイデンティティの法則に違反しています。
//Law of left identity is violated
// g(v) vs Promise.resolve(v).then(g)
// identity function saved under `then` prop
const v = ({then: x=>x({then: 1})})
// `g` returns the `then` prop from object wrapped in a promise
const g = (obj => Promise.resolve(obj.then))
g(v).then(res =>
console.log("g(v) returns", res))
// "g(v) returns" x => x({ then: 1 })
Promise.resolve(v).then(g)
.then(res =>
console.log("Promise.resolve(v).then(g) returns", res))
// "Promise.resolve(v).then(g) returns" 1
これは、resolveがthenプロパティの下の関数をコールバックとして扱い、thenチェーンの継続を引数として含むプロミスを作成するのではなく、引数として渡すためです。この方法では、ユニットのように機能せず、モナドの法則に違反します。
ただし、thenプロパティを含まない値では、モナドとして機能する必要があります。