Antworten:
Die Verwendung von Maybe
(oder seines Cousins, Either
der im Wesentlichen auf die gleiche Weise funktioniert, Sie jedoch anstelle von einen beliebigen Wert zurückgeben lässt Nothing
) dient einem etwas anderen Zweck als Ausnahmen. In Java ist das so, als hätte man eher eine aktivierte Ausnahme als eine Laufzeitausnahme. Es steht für etwas Erwartetes, mit dem Sie sich befassen müssen, und nicht für einen Fehler, den Sie nicht erwartet haben.
Eine Funktion wie indexOf
würde also einen Maybe
Wert zurückgeben, da Sie die Möglichkeit erwarten, dass das Element nicht in der Liste enthalten ist. Dies ähnelt der Rückkehr null
von einer Funktion, außer auf typsichere Weise, die Sie dazu zwingt, sich mit dem null
Fall zu befassen . Either
Dies funktioniert auf die gleiche Weise, mit der Ausnahme, dass Sie Informationen zurückgeben können, die mit dem Fehlerfall verknüpft sind, sodass sie einer Ausnahme ähnlicher sind als Maybe
.
Was sind die Vorteile des Maybe
/ Either
Ansatzes? Zum einen ist es ein erstklassiger Bürger der Sprache. Vergleichen wir eine Funktion mit einer Funktion, Either
die eine Ausnahme auslöst. Für den Ausnahmefall ist Ihre einzige wirkliche Möglichkeit eine try...catch
Aussage. Für die Either
Funktion können Sie vorhandene Kombinatoren verwenden, um die Flusssteuerung übersichtlicher zu gestalten. Hier einige Beispiele:
Nehmen wir zunächst an, Sie möchten mehrere Funktionen ausprobieren, die nacheinander fehlschlagen können, bis Sie eine erhalten, die dies nicht tut. Wenn Sie keine ohne Fehler erhalten, möchten Sie eine spezielle Fehlermeldung zurückgeben. Dies ist eigentlich ein sehr nützliches Muster, aber es wäre ein schrecklicher Schmerz try...catch
. Glücklicherweise können Sie, da Either
es sich nur um einen normalen Wert handelt, vorhandene Funktionen verwenden, um den Code viel klarer zu gestalten:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Ein weiteres Beispiel ist eine optionale Funktion. Angenommen, Sie müssen mehrere Funktionen ausführen, darunter eine, die versucht, eine Abfrage zu optimieren. Wenn dies fehlschlägt, möchten Sie, dass alles andere ausgeführt wird. Sie könnten Code schreiben wie:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Beide Fälle sind klarer und kürzer als die Verwendung try..catch
und vor allem semantischer. Wenn Sie eine Funktion wie <|>
oder verwenden, werden optional
Ihre Absichten viel klarer, als wenn Sie try...catch
immer Ausnahmen behandeln.
Beachten Sie auch , dass Sie nicht tun zu Wurf haben Sie den Code mit Zeilen wie if a == Nothing then Nothing else ...
! Der springende Punkt bei der Behandlung Maybe
und Either
als Monade ist, dies zu vermeiden. Sie können die Ausbreitungssemantik in die Bindefunktion kodieren, sodass Sie die Null- / Fehlerprüfungen kostenlos erhalten. Das einzige Mal, dass Sie explizit prüfen müssen, ist, ob Sie etwas anderes als ein Nothing
gegebenes zurückgeben möchten Nothing
, und selbst dann ist es einfach: Es gibt eine Reihe von Standard-Bibliotheksfunktionen, die diesen Code schöner machen.
Ein weiterer Vorteil ist, dass ein Maybe
/ Either
-Typ nur einfacher ist. Es ist nicht erforderlich, die Sprache um zusätzliche Schlüsselwörter oder Kontrollstrukturen zu erweitern - alles ist nur eine Bibliothek. Da es sich nur um normale Werte handelt, wird das Typensystem einfacher - in Java müssen Sie zwischen Typen (z. B. dem Rückgabetyp) und Effekten (z. B. throws
Anweisungen) unterscheiden, die Sie nicht verwenden würden Maybe
. Sie verhalten sich ebenso wie alle anderen benutzerdefinierten Typen - es ist kein spezieller Fehlerbehandlungscode erforderlich, der in die Sprache eingebettet ist.
Ein weiterer Sieg ist , dass Maybe
/ Either
sind functors und Monaden, welche Mittel sie die Vorteile der bestehenden Monade Ablaufsteuerungsfunktionen übernehmen kann (von denen es eine ganze Reihe) und im Allgemeinen spielen schön zusammen mit anderen Monaden.
Das heißt, es gibt einige Einschränkungen. Zum einen werden ungeprüfte Ausnahmen weder ersetzt Maybe
noch Either
ersetzt . Sie möchten eine andere Möglichkeit, um mit Dingen wie dem Teilen durch 0 umzugehen, einfach, weil es ein Problem wäre, wenn jede einzelne Division einen Wert zurückgibt.Maybe
Ein weiteres Problem besteht darin, dass mehrere Arten von Fehlern zurückgegeben werden (dies gilt nur für Either
). Mit Ausnahmen können Sie verschiedene Arten von Ausnahmen in derselben Funktion auslösen. mit Either
bekommst du nur einen typ. Dies kann durch Subtypisierung oder ein ADT überwunden werden, das alle Arten von Fehlern als Konstruktoren enthält (dieser zweite Ansatz wird normalerweise in Haskell verwendet).
Trotzdem bevorzuge ich den Maybe
/ Either
-Ansatz, weil ich ihn einfacher und flexibler finde.
OpenFile()
kann werfen FileNotFound
oder NoPermission
oder TooManyDescriptors
usw. A Keine trägt diese Information nicht.if None return None
-Stil-Anweisungen.Vor allem haben eine Ausnahme und eine Vielleicht-Monade unterschiedliche Zwecke - eine Ausnahme wird verwendet, um ein Problem zu kennzeichnen, eine Vielleicht-Monade nicht.
"Schwester, wenn ein Patient in Raum 5 ist, können Sie ihn bitten zu warten?"
(Beachten Sie das "Wenn" - dies bedeutet, dass der Arzt eine Vielleicht-Monade erwartet. )
None
Werte einfach weitergegeben werden). Ihr Punkt 5 ist nur irgendwie richtig… die Frage ist: Welche Situationen sind eindeutig außergewöhnlich? Wie sich herausstellt ... nicht viele .
bind
, dass das Testen auf None
keinen syntaktischen Aufwand entsteht. Ein sehr einfaches Beispiel: C # überlastet die Nullable
Operatoren nur angemessen. None
Auch bei Verwendung des Typs ist keine Überprüfung erforderlich. Natürlich wird die Prüfung noch durchgeführt (es ist typsicher), aber hinter den Kulissen und nicht Ihren Code überladen. Das Gleiche gilt in gewissem Sinne für Ihren Einwand gegen meinen Einwand gegen (5), aber ich bin damit einverstanden, dass er möglicherweise nicht immer zutrifft.
Maybe
als Monade darin, die Ausbreitung None
implizit zu machen . Dies bedeutet, dass Sie, wenn Sie einen None
bestimmten None
Code zurückgeben möchten , überhaupt keinen speziellen Code schreiben müssen. Sie müssen nur übereinstimmen, wenn Sie etwas Besonderes tun möchten None
. Sie brauchen nie eine if None then None
Art von Aussagen.
null
genau so (zB if Nothing then Nothing
) kostenlosMaybe
checkt, weil es eine Monade ist. Es ist in der Definition von bind ( >>=
) für codiert Maybe
.
Either
), die sich genau so verhalten Maybe
. Das Umschalten zwischen den beiden ist eigentlich ziemlich einfach, da Maybe
es sich eigentlich nur um einen Sonderfall handelt Either
. (In Haskell, könnte man sich vorstellen , Maybe
wie Either ()
.)
"Vielleicht" ist kein Ersatz für Ausnahmen. Ausnahmen sollen in Ausnahmefällen verwendet werden (zum Beispiel: Öffnen einer DB-Verbindung und der DB-Server ist nicht da, obwohl es sein sollte). "Vielleicht" dient zum Modellieren einer Situation, in der Sie möglicherweise einen gültigen Wert haben oder nicht. Angenommen, Sie erhalten einen Wert aus einem Wörterbuch für einen Schlüssel: Er kann vorhanden sein oder nicht - an keinem dieser Ergebnisse ist etwas "Außergewöhnliches".
Ich stimme Tikhons Antwort zu, aber ich denke, es gibt einen sehr wichtigen praktischen Punkt, den jeder vermisst:
Either
Mechanismus ist überhaupt nicht an Threads gekoppelt.Was wir heutzutage im wirklichen Leben sehen, ist, dass viele asynchrone Programmierlösungen eine Variante des Either
Stils der Fehlerbehandlung verwenden. Betrachten Sie Javascript- Versprechen , wie in einem dieser Links beschrieben:
Das Konzept der Versprechungen ermöglicht es Ihnen, wie folgt asynchronen Code zu schreiben (entnommen aus dem letzten Link):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Grundsätzlich ist ein Versprechen ein Objekt, das:
Da die systemeigene Ausnahmebedingungsunterstützung der Sprache nicht funktioniert, wenn Ihre Berechnung über mehrere Threads hinweg erfolgt, muss eine versprochene Implementierung einen Fehlerbehandlungsmechanismus bereitstellen, der sich als Monaden herausstellt, die denen von Haskell Maybe
/ Either
types ähneln .
Das System vom Typ Haskell fordert den Benutzer auf, die Möglichkeit von a zu bestätigen Nothing
, wohingegen Programmiersprachen häufig nicht erfordern, dass eine Ausnahme abgefangen wird. Das bedeutet, dass wir zur Kompilierungszeit wissen, dass der Benutzer nach einem Fehler gesucht hat.
throws NPE
jeder einzelnen Signatur und catch(...) {throw ...}
jedem einzelnen Methodenkörper ein hinzufügen müssen . Aber ich glaube, es gibt einen Markt für Überprüfungen in demselben Sinne wie für Vielleicht: Die Nullwertfähigkeit ist optional und wird im Typsystem nachverfolgt.
Die Vielleicht-Monade ist im Grunde die gleiche wie die Prüfung mit "Null bedeutet Fehler" in den meisten gängigen Sprachen (mit der Ausnahme, dass die Null geprüft werden muss) und weist im Wesentlichen dieselben Vor- und Nachteile auf.
Maybe
Zahlen durch Schreiben hinzufügen , a + b
ohne nachsehen zu müssen None
, und das Ergebnis ist wieder ein optionaler Wert.
Maybe
Typ , aber die Verwendung Maybe
als Monade fügt Syntaxzucker hinzu, mit dem sich nullartige Logik viel eleganter ausdrücken lässt.
Die Ausnahmebehandlung kann ein echtes Problem beim Factoring und Testen sein. Ich weiß, dass Python eine nette "with" -Syntax bietet, mit der Sie Ausnahmen ohne den starren "try ... catch" -Block abfangen können. Aber in Java zum Beispiel sind catch-Blöcke groß, ausführlich oder extrem ausführlich und schwer zu trennen. Darüber hinaus fügt Java das gesamte Rauschen um aktivierte und nicht aktivierte Ausnahmen hinzu.
Wenn Ihre Monade stattdessen Ausnahmen abfängt und sie als eine Eigenschaft des monadischen Raums behandelt (anstelle von Verarbeitungsfehlern), können Sie Funktionen, die Sie in diesen Raum einbinden, unabhängig davon, was sie werfen oder abfangen, mischen und zuordnen.
Wenn, noch besser, Ihre Monade Bedingungen verhindert, in denen Ausnahmen auftreten könnten (z. B. ein Null-Check in Vielleicht), dann noch besser. wenn ... dann ist es viel einfacher zu faktorisieren und zu testen als zu versuchen ... zu fangen.
Wie ich gesehen habe, verfolgt Go einen ähnlichen Ansatz, indem angegeben wird, dass jede Funktion zurückgibt (Antwort, Fehler). Das ist in etwa so, als würde man die Funktion in einen Monadenraum "heben", in dem der Kernantworttyp mit einer Fehleranzeige versehen ist, und Ausnahmen effektiv ausweichen und abfangen.