2016-11-07 5 views
2

Als ein funktionaler Javascript Entwickler mit nur einem vagen Verständnis von Haskell habe ich wirklich Schwierigkeiten Haskell Idiome wie Monaden zu verstehen. Als ich bei >>= der Funktion Beispiel aussehenWarum liefert die Bindung der Funktionsinstanz den ursprünglichen Wert an die nächste Berechnung?

(>>=) :: (r -> a) -> (a -> (r -> b)) -> r -> b 

instance Monad ((->) r) where 
f >>= k = \ r -> k (f r) r 

// Javascript: 

und ihre Anwendung mit Javascript

const bind = f => g => x => g(f(x)) (x); 

const inc = x => x + 1; 

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3); 


f(2); // 4 
f(5); // 15 

die monadische Funktion (a -> (r -> b)) (oder (a -> m b)) bietet eine Möglichkeit, die nächste Berechnung wählen auf dem vorherigen Ergebnis je. Allgemeiner scheint uns die monadische Funktion zusammen mit ihrem entsprechenden Operator die Fähigkeit zu geben zu definieren, was Funktionszusammensetzung in einem spezifischen Berechnungskontext bedeutet.

Umso überraschender ist es, dass die monadische Funktion nicht das Ergebnis der vorhergehenden Berechnung an die nachfolgende liefert. Stattdessen wird der ursprüngliche Wert übergeben. Ich würde erwarten, f(2)/f(5) zu 6/18, ähnlich wie normale Funktionszusammensetzung ergeben. Ist dieses Verhalten für Funktionen als Monaden spezifisch? Was versteh ich falsch?

+0

Die Faustregel lautet: 'bind (...) (x => ...)' nimmt den Wert der vorherigen Berechnung und bindet ihn an 'x'. Andere lambdas 'x => ...' (nicht nach einer Bindung) greifen stattdessen auf dasselbe implizite Nur-Lese-Argument zu, das am Anfang an 'f' übergeben wird. – chi

+3

Randnotiz: 'x => x <= 5 ? x => x * 2: x => x * 3' ist nicht lesbar, bitte verwenden Sie z. 'x => x <= 5 ? y => y * 2: z => z * 3' - ein Computer interessiert sich nicht für Alpha-Äquivalenz, aber Menschen tun ;-) – chi

+0

Weil es * muss * - diese Funktion ist die einzig gültige Umsetzung von eine Funktion, deren Typ 'forall rab ist. (r -> a) -> (a -> (r -> b)) -> r -> b '(außer undefiniert, natürlich). Die JavaScript-Funktion kann natürlich tun, was auch immer sie in der Abwesenheit von formalen Typen mag, aber dann wäre das Nachdenken darüber viel schwieriger (und nannte es "bind" wäre einfach falsch) – user2407038

Antwort

5

Ich denke, Ihre Verwirrung entsteht durch die Verwendung von Funktionen, die zu einfach sind. Insbesondere schreiben Sie

const inc = x => x + 1; 

, deren Typ eine Funktion, die Werte in der gleichen Raum als seine Eingabe zurückgibt. Nehmen wir an, inc beschäftigt sich mit ganzen Zahlen. Da sowohl seine Ein- und Ausgangs ganze Zahlen sind, wenn Sie eine andere Funktion foo haben, die ganze Zahlen nimmt, ist es einfach, mit dem Ausgang von inc als Eingang-foo vorzustellen.

Die reale Welt enthält jedoch aufregendere Funktionen. Betrachten Sie die Funktion tree_of_depth, die eine ganze Zahl annimmt und einen Baum mit Strings dieser Tiefe erstellt. (Ich werde nicht versuchen, es zu implementieren, weil ich nicht genug JavaScript, um eine überzeugende Arbeit davon zu tun.) Jetzt plötzlich ist es schwieriger, die Ausgabe von tree_of_depth als eine Eingabe an foo übergeben, da foo ist erwartet ganze Zahlen und tree_of_depth produziert Bäume, richtig? Das einzige, was wir an foo weiterleiten können, ist der Eingang zu tree_of_depth, denn das ist die einzige ganze Zahl, die wir herumliegen haben, selbst nach dem Lauf tree_of_depth.

Mal sehen, wie das in der Art Signatur Haskell manifestiert für bind:

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b) 

Diese besagt, dass (>>=) zwei Argumente übernimmt, die jeweils Funktionen. Die erste Funktion kann einen beliebigen alten Typ haben - sie kann einen Wert vom Typ r annehmen und einen Wert vom Typ a erzeugen. Insbesondere müssen Sie nicht versprechen, dass r und a die gleichen sind.Aber sobald Sie seinen Typ auswählen, ist der Typ des nächsten Funktionsarguments auf (>>=) beschränkt: Es muss eine Funktion von zwei Argumenten sein, deren Typen die gleichenr und a wie zuvor sind.

Jetzt können Sie sehen, warum wir den gleichen Wert vom Typ r auf diese beiden Funktionen übergeben haben: die erste Funktion erzeugt ein a, nicht eine aktualisierte r, so haben wir keinen anderen Wert vom Typ r an die weitergeben zweite Funktion! Anders als bei Ihrer Situation mit inc, wo die erste Funktion auch und r passiert, produzieren wir möglicherweise einen anderen sehr unterschiedlichen Typ.

Dies erklärt, warum Bindung muss so implementiert werden, wie es ist, aber vielleicht nicht erklären, warum diese Monade eine nützliche ist. An anderer Stelle wird darüber geschrieben. Der kanonische Anwendungsfall ist jedoch für Konfigurationsvariablen. Angenommen, Sie starten beim Programmstart eine Konfigurationsdatei; Für den Rest des Programms möchten Sie das Verhalten verschiedener Funktionen beeinflussen können, indem Sie Informationen aus dieser Konfiguration betrachten. In jedem Fall ist es sinnvoll, die gleichen Konfigurationsinformationen zu verwenden - sie müssen nicht geändert werden. Dann wird diese Monade nützlich: Sie können einen impliziten Konfigurationswert haben, und die Bindeoperation der Monade stellt sicher, dass die beiden Funktionen, die Sie sequenzieren, beide Zugriff auf diese Informationen haben, ohne sie manuell an beide Funktionen übergeben zu müssen.

P.S. Sie sagen

Es ist umso überraschender, dass die monadische Funktion das Ergebnis der vorherigen Berechnung nicht dem folgenden liefert.

, die ich etwas ungenau finden: in der Tat in m >>= f die Funktion fsowohl das Ergebnis m (als erstes Argument) und der ursprüngliche Wert (als zweites Argument) erhält.

+0

Große, umfangreiche Antwort, Danke! 'r' und' a' können natürlich anders sein. Und mit dem gegebenen Typ für ">> =" ist keine andere gültige Implementierung möglich. Was ich an '(->)' Beispielen von Monaden, Applikanten und Funktoren mag, ist, dass sie ein vertrautes Konzept (Kombinator) sind, das in einem anderen Konzept angewendet wird, was mir ziemlich fremd ist. Sie sind ein geeigneter Ausgangspunkt, um dieses neue Konzept zu verstehen. Das Programmieren auf mathematische Schnittstellen ist schwierig, besonders in Javascript, wo man einfach Dinge erstellen kann. Ich glaube, ich habe gerade einen weiteren Schritt nach vorne gemacht. Danke noch einmal! – ftor

+0

Funktionen können als eine Art Typkonstruktor angesehen werden, da sie ein 'r' in ein' a' eines anderen Typs transformieren können. Wenn 'ma' /' mb' innerhalb '(>> =) :: ma -> (a -> mb) -> mb' sind Funktionen vom Typ' (r -> a) '/' (r -> b) ', gibt es keine Alternative für' (r -> b) ', um das Ergebnis 'a' der vorherigen Operation zu ignorieren. Vorausgesetzt, dass '(r -> a)' vom Typ '(r -> r)' ist, können Sie den 'K'-Kombinator verwenden, um den berechneten Wert an' (r -> b) 'zu übergeben, weil' K '' ist return 'für die Funktionsinstanz. Entschuldigung, wenn ich mich wiederholen sollte, aber ich habe es endlich verstanden. – ftor

2

Allgemeiner die monadische Funktion zusammen mit seinem entsprechenden binden Operator scheint uns die Möglichkeit zu geben, zu definieren, welche Funktion Zusammensetzung bedeutet, in einem bestimmten Berechnungskontext.

Ich bin mir nicht sicher, was Sie mit der "monadischen Funktion" meinen. Monaden (die in Haskell aus einer Bind-Funktion und einer reinen Funktion bestehen) lassen Sie ausdrücken, wie eine Reihe von monadischen Aktionen miteinander verkettet werden können. ((<=<) ist das Monadequivalent der Zusammensetzung, entspricht (.) für die Identity Monade). In diesem Sinne erhalten Sie Art von Zusammensetzung, aber nur Zusammensetzung von Aktionen (Funktionen der Form).

(Dies wird weiter abstrahiert in den Kleisli newtype um Funktionen des Typs a -> m b. Seine category instance wirklich können Sie die Sequenzierung von monadischen Aktionen als Zusammensetzung schreiben.)

ich erwarten würde f (2)/f (5), um 6/18 zu ergeben, ähnlich der normalen Funktionszusammensetzung.

Dann können Sie einfach normale Funktionszusammensetzung verwenden! Verwenden Sie keine Monade, wenn Sie keine benötigen.

Umso überraschender ist es, dass die monadische Funktion das Ergebnis der vorhergehenden Berechnung nicht an die nachfolgende liefert. Stattdessen wird der ursprüngliche Wert übergeben. ... Funktioniert dieses für spezifische Verhalten als Monade?

Ja, es ist es. Die Monade Monad ((->) r) ist auch bekannt als "Reader Monad", weil es nur aus seiner Umgebung liest. Das heißt, soweit Monaden betrifft, sind Sie sind immer noch die monadischen Ergebnis der vorherigen Aktion auf die nachfolgende übergeben - aber diese Ergebnisse sind selbst Funktionen!

+0

OK, ich werde die monadische Funktion nicht mehr verwenden, sondern _action_ oder _kleinli arrow_. Vielen Dank! – ftor

2

Wie bereits von chi erwähnt, diese Linie

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3); 

wäre klarer, wenn so etwas wie

const f = bind(inc) (x => x <= 5 ? y => y * 2 : y => y * 3); 

die Monad Instanz für Funktionen im Grunde die Monade Reader ist. Sie haben einen Wert x => x + 1, der von einer Umgebung abhängt (fügt der Umgebung 1 hinzu).

Sie haben auch eine Funktion, die abhängig von ihrer Eingabe einen Wert zurückgibt, der von einer Umgebung (y => y * 2) oder einem anderen Wert abhängt, der von einer Umgebung abhängt (y => y * 3).

In Ihrer bind verwenden Sie nur das Ergebnis x => x + 1 bis Wählen Sie zwischen diesen beiden Funktionen. Sie geben das vorherige Ergebnis nicht direkt zurück. Aber Sie könnten, wenn Sie konstante Funktionen zurück die ihre Umgebungen ignoriert und hat einen festen Wert nur in Abhängigkeit von dem vorherigen Ergebnis:

const f = bind(inc) (x => x <= 5 ? _ => x * 2 : _ => x * 3); 

(nicht sicher über die Syntax)

+0

Danke für deine Antwort. Ich denke, dein letztes Beispiel ist eher eine ungewöhnliche Verwendung von 'bind', oder? – ftor

+0

Überhaupt nicht! Jede 'Monade'-Instanzdefinition benötigt neben' bind' eine weitere Funktion, die einen reinen Wert in einen "neutralen" Kontext stellt. In Haskell heißt diese andere Funktion (verwirrend) 'return'. 'return' für die Funktion/Reader-Monaden nur in der konstanten Funktion, die die Umgebung empfängt und ignoriert. – danidiaz

+1

Ach, 'return' der' (->) 'Instanz ist nur der' K' Kombinator. Ich habs! – ftor

Verwandte Themen