Einige Sprachen behaupten, "keine Laufzeitausnahmen" zu haben, was einen klaren Vorteil gegenüber anderen Sprachen darstellt, in denen sie vorkommen.
Ich bin in der Sache verwirrt.
Die Laufzeitausnahme ist meines Wissens nur ein Werkzeug und wird gut verwendet:
- Sie können "schmutzige" Zustände kommunizieren (auf unerwartete Daten werfen)
- Wenn Sie einen Stapel hinzufügen, können Sie auf die Fehlerkette verweisen
- Sie können zwischen Unordnung (z. B. Rückgabe eines leeren Werts bei ungültiger Eingabe) und unsicherer Verwendung unterscheiden, die die Aufmerksamkeit eines Entwicklers erfordert (z. B. Ausnahmefehler bei ungültiger Eingabe).
- Sie können Details zu Ihrem Fehler hinzufügen, indem Sie die Ausnahmemeldung mit weiteren hilfreichen Details versehen, die Ihnen (theoretisch) beim Debuggen helfen.
Andererseits finde ich es wirklich schwierig, eine Software zu debuggen, die Ausnahmen "verschluckt". Z.B
try {
myFailingCode();
} catch {
// no logs, no crashes, just a dirty state
}
Die Frage ist also: Was ist der starke theoretische Vorteil, "keine Laufzeitausnahmen" zu haben?
Beispiel
Keine Laufzeitfehler in der Praxis. Nein null. Nein undefiniert ist keine Funktion.
Antworten:
Ausnahmen haben eine extrem einschränkende Semantik. Sie müssen genau dort gehandhabt werden, wo sie geworfen werden, oder im direkten Aufrufstapel nach oben, und es gibt beim Kompilieren keine Anzeige für den Programmierer, wenn Sie dies vergessen.
Vergleichen Sie dies mit Elm, wo Fehler als Ergebnisse oder Maybes codiert werden , die beide Werte sind . Das bedeutet, dass Sie einen Compilerfehler erhalten, wenn Sie den Fehler nicht behandeln. Sie können sie in einer Variablen oder sogar in einer Sammlung speichern, um ihre Bearbeitung auf einen geeigneten Zeitpunkt zu verschieben. Sie können eine Funktion erstellen, um die Fehler anwendungsspezifisch zu behandeln, anstatt sehr ähnliche Try-Catch-Blöcke überall zu wiederholen. Sie können sie zu einer Berechnung verketten, die nur dann erfolgreich ist, wenn alle ihre Teile erfolgreich sind, und sie müssen nicht in einen Try-Block gepackt werden. Sie sind nicht an die eingebaute Syntax gebunden.
Dies ist nichts anderes als "Ausnahmen schlucken". Es macht Fehlerbedingungen im Typsystem explizit und bietet viel flexiblere alternative Semantik, um damit umzugehen.
Betrachten Sie das folgende Beispiel. Sie können dies in http://elm-lang.org/try einfügen, wenn Sie es in Aktion sehen möchten.
Beachten Sie, dass
String.toInt
in dercalculate
Funktion die Möglichkeit eines Fehlers besteht. In Java kann dies zu einer Laufzeitausnahme führen. Wenn es Benutzereingaben liest, hat es eine ziemlich gute Chance. Elm zwingt mich stattdessen, mit a umzugehenResult
, aber ich muss feststellen, dass ich mich nicht sofort darum kümmern muss. Ich kann die Eingabe verdoppeln und in eine Zeichenfolge konvertieren und dann diegetDefault
Funktion auf fehlerhafte Eingaben überprüfen . Dieser Ort ist für die Prüfung viel besser geeignet als der Punkt, an dem der Fehler aufgetreten ist, oder aufwärts im Aufrufstapel.Die Art und Weise, wie der Compiler unsere Hand erzwingt, ist auch viel feiner als die von Java geprüften Ausnahmen. Sie müssen eine sehr spezielle Funktion verwenden
Result.withDefault
, um den gewünschten Wert zu extrahieren. Technisch gesehen könnte man diese Art von Mechanismus missbrauchen, aber es macht nicht viel Sinn. Da Sie die Entscheidung verschieben können, bis Sie eine gute Standard- / Fehlermeldung kennen, gibt es keinen Grund, sie nicht zu verwenden.quelle
That means you get a compiler error if you don't handle the error.
- Nun, das war der Grund für Checked Exceptions in Java, aber wir alle wissen, wie gut das geklappt hat.Maybe
,Either
etc. Elm sieht aus wie es ist eine Seite aus Sprachen wie ML, OCaml oder Haskell nehmen.x = some_func()
, kann ich nicht habe , etwas zu tun , wenn ich den Wert untersuchen willx
, in diesem Fall kann ich überprüfen , ob ich einen Fehler oder einen „gültigen“ Wert habe; Darüber hinaus ist es ein statischer Typfehler, zu versuchen, einen anstelle des anderen zu verwenden, daher kann ich das nicht tun. Wenn die Typen von Elm wie andere funktionale Sprachen funktionieren, kann ich tatsächlich Werte aus verschiedenen Funktionen zusammensetzen, bevor ich überhaupt weiß, ob es sich um Fehler handelt oder nicht! Dies ist typisch für FP-Sprachen.Um diese Aussage zu verstehen, müssen wir zunächst verstehen, was uns ein statisches Typensystem einkauft. Im Grunde ist das, was uns ein statisches Typsystem bietet, eine Garantie: Wenn der Programmtyp überprüft wird, kann eine bestimmte Klasse von Laufzeitverhalten nicht auftreten.
Das klingt bedrohlich. Nun, ein Typprüfer ähnelt einem Theoremprüfer. (Nach dem Curry-Howard-Isomorphismus sind sie dasselbe.) Eine Besonderheit bei Theoremen ist, dass Sie, wenn Sie einen Theorem beweisen, genau beweisen, was der Theorem sagt, nicht mehr. (Das ist zum Beispiel der Grund, warum, wenn jemand sagt "Ich habe dieses Programm als richtig erwiesen", Sie immer fragen sollten "Bitte definieren Sie" richtig ".) Dasselbe gilt für Typsysteme. Wenn wir sagen "ein Programm ist typsicher", meinen wir nicht, dass kein möglicher Fehler auftreten kann. Wir können nur sagen, dass die Fehler, die das Typensystem zu verhindern verspricht, nicht auftreten können.
Programme können also unendlich viele verschiedene Laufzeitverhalten haben. Von diesen sind unendlich viele nützlich, aber auch unendlich viele sind "falsch" (für verschiedene Definitionen von "Korrektheit"). Mit einem statischen Typsystem können wir nachweisen, dass ein bestimmter endlicher, fester Satz von unendlich vielen falschen Laufzeitverhalten nicht auftreten kann.
Der Unterschied zwischen verschiedenen Typsystemen besteht grundsätzlich darin, in welchen, wie vielen und wie komplexen Laufzeitverhalten sie sich als nicht vorkommend erweisen können. Schwache Systeme wie das von Java können nur sehr grundlegende Dinge beweisen. Beispielsweise kann Java beweisen, dass eine Methode, die als "returning a" eingegeben wurde,
String
kein "return a" zurückgeben kannList
. Aber zum Beispiel kann es nicht beweisen, dass die Methode nicht zurückkehrt. Es kann auch nicht nachgewiesen werden, dass die Methode keine Ausnahme auslöst. Und es kann nicht beweisen, dass es nicht das Falsche zurückgibtString
- jedesString
wird die Typprüfung erfüllen. (Und natürlich, auchnull
wird es genügen auch.) Es gibt auch ganz einfache Dinge , dass Java nicht beweisen können, weshalb wir Ausnahmen haben wieArrayStoreException
,ClassCastException
oder jedermanns Liebling, derNullPointerException
.Leistungsstärkere Typsysteme wie Agda's können auch Dinge wie "die Summe der beiden Argumente zurückgeben" oder "die sortierte Version der als Argument übergebenen Liste zurückgeben" beweisen.
Was die Entwickler von Elm unter der Aussage verstehen, dass sie keine Laufzeitausnahmen haben, ist, dass das Typsystem von Elm das Fehlen (eines signifikanten Teils) von Laufzeitverhalten nachweisen kann, das in anderen Sprachen nicht nachgewiesen werden kann und daher möglicherweise nicht auftritt zu fehlerhaftem Verhalten zur Laufzeit (was im besten Fall eine Ausnahme bedeutet, im schlimmsten Fall einen Absturz und im schlimmsten Fall überhaupt keinen Absturz, keine Ausnahme und nur ein stillschweigend falsches Ergebnis).
So sind sie nicht sagen : „Wir implementieren keine Ausnahmen“. Sie sagen, "Dinge, mit denen Laufzeitausnahmen in typischen Sprachen, mit denen typische Programmierer bei Elm Erfahrung haben würden, vom Typensystem erfasst werden". Natürlich wird jemand, der aus Idris, Agda, Guru, Epigramm, Isabelle / HOL, Coq oder ähnlichen Sprachen kommt, Elm im Vergleich als ziemlich schwach ansehen. Die Anweisung richtet sich eher an typische Java-, C♯-, C ++ -, Objective-C-, PHP-, ECMAScript-, Python-, Ruby-, Perl- usw. Programmierer.
quelle
Elm kann aus demselben Grund keine Laufzeitausnahme garantieren. C kann keine Laufzeitausnahme garantieren: Die Sprache unterstützt das Konzept der Ausnahmen nicht.
Elm hat eine Möglichkeit, Fehlerzustände zur Laufzeit zu signalisieren, aber bei diesem System handelt es sich nicht um Ausnahmen, sondern um "Ergebnisse". Eine möglicherweise fehlgeschlagene Funktion gibt ein "Ergebnis" zurück, das entweder einen regulären Wert oder einen Fehler enthält. Elms ist stark typisiert, daher ist dies im Typensystem explizit. Wenn eine Funktion immer eine Ganzzahl zurückgibt, hat sie den Typ
Int
. Wenn es jedoch entweder eine Ganzzahl zurückgibt oder fehlschlägt, lautet der RückgabetypResult Error Int
. (Die Zeichenfolge ist die Fehlermeldung.) Dies zwingt Sie dazu, beide Fälle an der Anrufstelle explizit zu behandeln.Hier ist ein Beispiel aus der Einführung (etwas vereinfacht):
Die Funktion
toInt
kann fehlschlagen, wenn die Eingabe nicht syntaktisch analysiert werden kann. Daher lautet der RückgabetypResult String int
. Um den tatsächlichen ganzzahligen Wert zu erhalten, müssen Sie ihn per Mustervergleich "entpacken", was Sie wiederum dazu zwingt, beide Fälle zu behandeln.Ergebnisse und Ausnahmen machen grundsätzlich das Gleiche, der wichtige Unterschied sind die "Vorgaben". Ausnahmen sprudeln und beenden das Programm standardmäßig, und Sie müssen sie explizit abfangen, wenn Sie sie behandeln möchten. Das Ergebnis ist der andere Weg - Sie sind gezwungen, standardmäßig damit umzugehen, sodass Sie sie explizit ganz nach oben übergeben müssen, wenn Sie möchten, dass sie das Programm beenden. Es ist leicht zu erkennen, wie dieses Verhalten zu robusterem Code führen kann.
quelle
doSomeStuff(x: Int): Int
. Normalerweise erwarten Sie, dass es eine zurückgibtInt
, aber kann es auch eine Ausnahme auslösen? Ohne einen Blick auf den Quellcode zu werfen, kann man es nicht wissen. Im Gegensatz dazu kann eine Sprache B, die Fehler über Typen codiert, dieselbe Funktion haben, die folgendermaßen deklariert wurde:doSomeStuff(x: Int): ErrorOrResultOfType<Int>
(In Elm wird dieser Typ tatsächlich benanntResult
). Anders als im ersten Fall ist jetzt sofort ersichtlich, ob die Funktion fehlschlagen kann, und Sie müssen sie explizit behandeln.this is how you program in languages such as ML or Haskell
In Haskell ja; ML, nein. Robert Harper, ein wichtiger Mitarbeiter von Standard ML und Programmiersprachenforscher, hält Ausnahmen für nützlich . Fehlertypen können die Funktionszusammensetzung beeinträchtigen, wenn Sie sicherstellen können, dass kein Fehler auftritt. Ausnahmen haben auch unterschiedliche Leistung. Sie zahlen nicht für Ausnahmen, die nicht ausgelöst werden, sondern für die Überprüfung der Fehlerwerte jedes Mal, und Ausnahmen sind eine natürliche Methode, um das Zurückverfolgen in einigen Algorithmen auszudrückenBeachten Sie zunächst, dass Ihr Beispiel für das "Verschlucken" von Ausnahmen im Allgemeinen eine schreckliche Praxis ist und keinerlei Beziehung dazu hat, keine Laufzeitausnahmen zu haben. Wenn Sie darüber nachdenken, ist ein Laufzeitfehler aufgetreten, Sie haben ihn jedoch ausgeblendet und nichts dagegen unternommen. Dies führt häufig zu schwer verständlichen Fehlern.
Diese Frage kann auf verschiedene Arten interpretiert werden, aber da Sie Elm in den Kommentaren erwähnt haben, ist der Kontext klarer.
Elm ist unter anderem eine statisch typisierte Programmiersprache. Einer der Vorteile dieser Art von Typsystemen besteht darin, dass viele Fehlerklassen (wenn auch nicht alle) vom Compiler abgefangen werden, bevor das Programm tatsächlich verwendet wird. Einige Arten von Fehlern können in Typen codiert werden (z. B. Elms
Result
undTask
) , anstatt als Ausnahmen geworfen zu werden. Dies ist, was die Designer von Elm meinen: Viele Fehler werden zur Kompilierungszeit statt zur Laufzeit abgefangen, und der Compiler wird Sie zwingen, mit ihnen umzugehen, anstatt sie zu ignorieren und auf das Beste zu hoffen. Es ist klar, warum dies ein Vorteil ist: Es ist besser, dass der Programmierer auf ein Problem aufmerksam wird, bevor der Benutzer es tut.Beachten Sie, dass Fehler auf andere, weniger überraschende Weise codiert werden, wenn Sie keine Ausnahmen verwenden. Aus Elms Dokumentation :
Elm-Designer behaupten ein bisschen kühn, dass es keine Laufzeitausnahmen gibt , obwohl sie es mit "in der Praxis" qualifizieren. Was sie wahrscheinlich bedeuten, ist "weniger unerwartete Fehler als wenn Sie in Javascript codieren".
quelle
Result
undTask
die sehen sehr ähnlich dem bekannterenEither
undFuture
aus anderen Sprachen. Im Gegensatz zu Ausnahmen können Werte dieser Typen kombiniert werden, und Sie müssen sie irgendwann explizit behandeln: Stellen sie einen gültigen Wert oder einen Fehler dar? Ich lese keine Gedanken, aber dieser Mangel an Überraschung für den Programmierer ist wahrscheinlich das, was Elm-Designer mit "keine Laufzeitausnahmen" meinten :)Elm behauptet:
Aber Sie fragen über Laufzeit Ausnahmen . Es besteht ein Unterschied.
In Elm gibt nichts ein unerwartetes Ergebnis zurück. Sie können KEIN gültiges Programm in Elm schreiben, das Laufzeitfehler erzeugt. Sie brauchen also keine Ausnahmen.
Die Frage sollte also lauten:
Wenn Sie Code schreiben können, der niemals Laufzeitfehler aufweist, werden Ihre Programme niemals abstürzen.
quelle