Ich habe etwas über funktionale Programmierung gelernt und bin auf Monaden, Functors und Applicatives gestoßen.
Nach meinem Verständnis gelten die folgenden Definitionen:
a) (A => B) => C [A] => C [B] | Functor
b) (A => C [B]) => C [A] => C [B] | Monade
c) (C [A => B]) => C [A] => C [B] | Anwendbar
(Referenz: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/ )
Darüber hinaus verstehe ich, dass eine Monade ein Sonderfall eines Functors ist. Wie in, wendet es eine Funktion an, die einen umschlossenen Wert auf einen umschriebenen Wert und einen umschlossenen Wert zurückgibt.
Wenn wir Promise.then(func)
verwenden, übergeben wir dem Promise (d. H. C [A]) eine Funktion, die normalerweise die Signatur A => B
hat, und geben ein anderes Promise (d. H. C [B]) zurück. Ich dachte also, ein Versprechen wäre nur ein Functor und keine Monade, da func
B und nicht C [B] zurückgibt.
Beim Googeln habe ich jedoch herausgefunden, dass ein Versprechen nicht nur ein Functor ist, sondern auch eine Monade. Ich frage mich, warum, da func
keinen umschlossenen Wert C [B] zurückgibt, sondern nur B. Was fehlt mir?
UDATE. In dieser neuen Bibliothek finden Sie Funktionsprüfungs- und Monadenoperatoren für einfache, auf Rückrufen basierende Funktionen, bei denen die im Folgenden beschriebenen Probleme mit theneables nicht auftreten:
https://github.com/dmitriz/cpsfy
Es ist kein Funktor, weil das Kompositionserhaltungsgesetz (Senden von Kompositionen von Funktionen an Kompositionen ihrer Bilder) verletzt wird:
promise.then(x => g(f(x)))
ist NICHT gleichbedeutend mit
promise.then(f).then(g)
Was dies in der Praxis bedeutet, kann niemals sicher umgestaltet werden
promise
.then(x => f(x))
.then(y => g(y))
zu
promise
.then(x => g(f(x))
wie es gewesen wäre, wäre Promise
ein functor.
Beweis der Verletzung des Functor Law. Hier ein Gegenbeispiel:
// Functor-Zusammensetzungserhaltungsgesetz: // Versprechen dann (f) dann (g) vs. Versprechen dann (x => g (f (x))) // f übernimmt die Funktion `x` // und speichert sie im Objekt unter` then` prop: const f = x => ({then: x}) // g gibt die `then`-Requisite vom Objekt const g = obj => obj.then // h = compose zurück (g, f) ist die Identität const h = x => g (f (x)) // Versprechen erfüllen mit der Identitätsfunktion const Promise = Promise.resolve (a => a) // Dieses Versprechen wird mit der Identitätsfunktion Promise.then (h) .then ( res => { console.log ("then (h) return:", res) }) // => "then (h) return:" a = > a // aber dieses Versprechen wird niemals erfüllt Versprechen, wenn (f) . dann (g) . dann (res => { console.log ("then (f) .then (g) returniert:", res) }) // => ??? // denn dieses ist nicht: versprechen dann (f) dann (res => { console.log ("dann (f) gibt zurück:", res) })
Hier ist dieses Beispiel für Codepen: https://codepen.io/dmitriz/pen/QrMawp?editors=0011
Da die Komposition h
die Identitätsfunktion ist, nimmt promise.then(h)
einfach den Zustand von promise
an, der bereits mit der Identität a => a
Erfüllt ist.
Andererseits gibt f
das sogenannte thenable zurück:
1.2. "Thenable" ist ein Objekt oder eine Funktion, die eine then-Methode definiert.
Um das Funktionsgesetz aufrechtzuerhalten, müsste .then
Einfach das Ergebnis f(x)
versprechen. Stattdessen erfordert Promise Spec ein anderes Verhalten, wenn die Funktion in .then
Ein "thenable" zurückgibt. Gemäß 2.3.3. wird die unter der Taste then
gespeicherte Identitätsfunktion id = a => a
Aufgerufen als
id(resolvePromise, rejectPromise)
wobei resolvePromise
und rejectPromise
zwei Rückruffunktionen sind, die durch das Versprechen-Auflösungsverfahren bereitgestellt werden. Aber dann muss eine dieser Rückruffunktionen aufgerufen werden, um aufgelöst oder abgelehnt zu werden, was niemals geschieht! Das resultierende Versprechen bleibt also in der Warteschleife.
In diesem Beispiel wird promise.then(x => g(f(x)))
mit der Identitätsfunktion a => a
Erfüllt, während promise.then(f).then(g)
für immer im ausstehenden Zustand bleibt. Daher sind diese beiden Versprechungen nicht gleichwertig, und daher wird das Funktorgesetz verletzt.
Weil sogar das natürliche Transformationsgesetz aus Pointed Functor Spec, das Teil des Seins Applicative (das Homomorphismusgesetz) ist, verletzt wird:
Promise.resolve(g(x)) is NOT equivalent to Promise.resolve(x).then(g)
Beweis. Hier ist ein Gegenbeispiel:
// Identitätsfunktion, die unter `then` prop const v = ({then: a => a}) //` g` returns `gespeichert wurde dann ist `prop from object const g = obj => obj.then //` g (v) `die Identitätsfunktion Promise.resolve ( g (v)). then (res => { console.log ("resolve (g (v)) return:", res) }) // = > "resolve (g (v)) gibt Folgendes zurück:" a => a // `v` wird in ein Versprechen eingewickelt, das für immer aussteht // da es niemals aufruft Jeder der Rückrufe Promise.resolve (v) .then (g) .then (res => { console.log ("resolve (v) .then (g) returns:", res) }) // => ???
Dieses Beispiel für Codepen: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011
Auch in diesem Beispiel wird ein Versprechen erfüllt, während das andere noch aussteht. Daher sind die beiden Versprechen in keiner Weise gleichwertig und verstoßen gegen das Gesetz.
AKTUALISIEREN.
Es scheint eine Verwechslung zwischen dem Versprechen , ein Functor/Applicative/Monad zu sein , und den Möglichkeiten, dies zu tun es wie , indem es seine Methoden ändert oder neue hinzufügt. Für einen Functor muss jedoch bereits eine map
-Methode (nicht unbedingt unter diesem Namen) bereitgestellt sein, und es hängt eindeutig von der Wahl dieser Methode ab, ob Sie ein Functor sind. Der tatsächliche Name der Methode spielt keine Rolle, solange die Gesetze eingehalten werden.
Für die Versprechungen ist .then
Die natürlichste Wahl, bei der das Functor-Gesetz, wie unten erläutert, nicht eingehalten wird. Keine der anderen Promise-Methoden würde es in irgendeiner denkbaren Weise zu einem Functor machen, soweit ich das beurteilen kann.
Es ist eine andere Frage, ob andere Methoden definiert werden können, die den Gesetzen entsprechen. Die einzige mir bekannte Implementierung in dieser Richtung ist die Creed-Bibliothek .
Aber es gibt einen beachtlichen Preis zu zahlen : Es muss nicht nur eine völlig neue map
-Methode definiert werden, sondern auch die Versprechungsobjekte selbst zu ändern: Ein creed
Versprechen kann einen "theneable" als Wert enthalten, während das native JS-Versprechen dies nicht kann. Diese Änderung ist wesentlich und notwendig, um Gesetzesverstöße in den nachfolgend erläuterten Beispielen zu vermeiden. Insbesondere ist mir keine Möglichkeit bekannt, das Versprechen in einen Functor (oder eine Monade) zu verwandeln, ohne solche grundlegenden Änderungen vorzunehmen.
Promise
ist (sehr ähnlich) eine Monade, weil then
überladen ist.
Wenn wir Promise.then (func) verwenden, übergeben wir dem Promise (d. H. C [A]) eine Funktion, die normalerweise die Signatur A => B hat, und geben ein anderes Promise zurück (d. H. C [B]). Ich dachte also, dass ein Versprechen nur ein Functor und keine Monade sein würde, da func B und nicht C [B] zurückgibt.
dies gilt für then(Promise<A>, Func<A, B>) : Promise<B>
(wenn Sie meinen Pseudocode für Javascript-Typen entschuldigen, beschreibe ich Funktionen, als ob this
das erste Argument wäre)
die Promise-API liefert jedoch eine weitere Signatur für then
, then(Promise<A>, Func<A, Promise<B>>) : Promise<B>
. Diese Version passt offensichtlich zur Signatur für Monadic Bind (>>=
). Probieren Sie es aus, es funktioniert.
das Anpassen der Signatur für eine Monade bedeutet jedoch nicht, dass Promise eine Monade ist . es muss auch die algebraischen Gesetze für Monaden erfüllen.
die Gesetze, die eine Monade erfüllen muss, sind das Gesetz der Assoziativität
(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) )
und die Gesetze der linken und rechten Identität
(return v) >>= f ≡ f v
m >>= return ≡ m
in 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
);
Ich denke, jeder, der mit Versprechungen vertraut ist, kann sehen, dass all dies wahr sein sollte, aber Sie können es auch selbst versuchen.
da Promise eine Monade ist, können wir ap
ableiten und daraus auch eine Anwendung ableiten, die uns eine sehr nette Syntax mit ein wenig schlecht beratenem Hackery gibt:
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)
Versprechungen behandeln Objekte, die eine then-Eigenschaft enthalten, die eine Funktion als Sonderfall darstellt. Aus diesem Grund verletzen sie das Gesetz der linken Identität wie folgt:
//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
Dies geschieht, weil die Funktion unter der then-Eigenschaft von resolve als Rückruf behandelt wird und die Fortsetzung der then-Kette als Argument übergeben wird, anstatt ein Versprechen zu erstellen, das sie enthält. Auf diese Weise funktioniert es nicht als Einheit und verstößt gegen die Monadengesetze.
Bei Werten, die keine then -Eigenschaft enthalten, sollte sie jedoch als Monade fungieren.