wake-up-neo.com

Warum sind Versprechen Monaden?

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?

12
Jack Spar

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


Das JS-Versprechen ist weder ein Functor noch ein Applicative noch eine Monade

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

Erläuterung

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.

Fazit

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.


Das Versprechen ist weder ein Monade noch ein Antragsteller

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

Fazit

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.

Was bedeutet es genau, ein Functor zu sein?

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.

Methoden ändern oder hinzufügen

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.

29
Dmitri Zaitsev

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)
4
colinro

Versprechen sind keine Monaden über Objekte, die dann eine Eigenschaft enthalten

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

Beispiel für Codepen

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.

0
Marty Gentillon