Der sauberste Weg, Fehler in Haskell zu melden

22

Ich arbeite daran, Haskell zu lernen, und habe drei verschiedene Möglichkeiten kennengelernt, um mit Fehlern in den von mir geschriebenen Funktionen umzugehen:

  1. Ich kann einfach schreiben error "Some error message.", was eine Ausnahme auslöst.
  2. Ich kann meine Funktion zurückgeben lassen Maybe SomeType, wo ich zurückgeben kann oder nicht, was ich zurückgeben möchte.
  3. Ich kann meine Funktion zurückgeben lassen Either String SomeType, wobei ich entweder eine Fehlermeldung oder das zurückgeben kann, wonach ich gebeten wurde.

Meine Frage lautet: Welche Methode zum Umgang mit Fehlern soll ich verwenden und warum? Vielleicht sollte ich je nach Kontext unterschiedliche Methoden anwenden?

Mein aktuelles Verständnis ist:

  • Es ist "schwierig", mit Ausnahmen in rein funktionalem Code umzugehen, und in Haskell möchte man die Dinge so rein funktional wie möglich halten.
  • Das Zurückgeben Maybe SomeTypeist die richtige Vorgehensweise, wenn die Funktion entweder fehlschlägt oder erfolgreich ist (dh es gibt keine unterschiedlichen Möglichkeiten, wie sie fehlschlagen kann ).
  • Das Zurückgeben Either String SomeTypeist die richtige Vorgehensweise, wenn eine Funktion auf verschiedene Arten fehlschlagen kann.
CmdrMoozy
quelle

Antworten:

32

Okay, erste Regel zur Fehlerbehandlung in Haskell: Niemals verwendenerror .

Es ist einfach in jeder Hinsicht schrecklich. Es existiert nur als Akt der Geschichte und die Tatsache, dass Prelude es benutzt, ist schrecklich. Benutze es nicht.

Die einzig denkbare Zeit, die Sie nutzen könnten, ist, wenn etwas so schrecklich ist, dass etwas mit dem eigentlichen Realitätsgefüge nicht in Einklang stehen muss, wodurch das Ergebnis Ihres Programms in Frage gestellt wird.

Nun ist die Frage wird Maybevs Either. Maybeeignet sich gut für etwas head, das einen Wert zurückgeben kann oder nicht, aber es gibt nur einen möglichen Grund für das Scheitern. Nothingsagt so etwas wie "es ist kaputt gegangen und du weißt schon warum". Einige würden sagen, es deutet auf eine Teilfunktion hin.

Die robusteste Form der Fehlerbehandlung ist Either+ ein Fehler-ADT.

Zum Beispiel habe ich in einem meiner Hobby-Compiler so etwas wie

data CompilerError = ParserError ParserError
                   | TCError TCError
                   ...
                   | ImpossibleError String

data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
             | MissingDefinition Name
             | InfiniteType Ty
             ...

type ErrorM m = ExceptT CompilerError m -- from MTL

Jetzt definiere ich eine Reihe von Fehlertypen und verschachtele sie so, dass ich einen herrlichen Fehler der obersten Ebene habe. Dies kann ein Fehler in jeder Phase der Kompilierung sein oder ein Fehler ImpossibleError, der auf einen Compiler-Fehler hinweist.

Bei jedem dieser Fehlertypen wird versucht, so lange wie möglich Informationen für hübsches Drucken oder andere Analysen zu speichern. Noch wichtiger ist, dass ich durch das Fehlen eines Strings testen kann, dass das Ausführen eines falsch geschriebenen Programms durch die Typprüfung tatsächlich einen Vereinigungsfehler erzeugt! Sobald etwas a ist String, ist es für immer verschwunden und alle darin enthaltenen Informationen sind für den Compiler / die Tests undurchsichtig, daher Either Stringist es auch nicht großartig.

Schließlich packe ich diesen Typ in ExceptTeinen neuen Monadentransformator von MTL. Dies ist im Wesentlichen EitherTund kommt mit einer schönen Reihe von Funktionen zum Werfen und Auffangen von Fehlern auf eine reine, angenehme Art und Weise.

Schließlich ist zu erwähnen, dass Haskell über die Mechanismen verfügt, mit denen Ausnahmen wie in anderen Sprachen behandelt werden können IO. Ich weiß, dass manche Leute diese gern für IOschwere Anwendungen verwenden, bei denen möglicherweise alles versagt, aber so selten, dass sie nicht gerne darüber nachdenken. Ob Sie diese unreinen Ausnahmen verwenden oder einfach nur, ExceptT Error IOist Geschmackssache. Ich persönlich entscheide mich dafür, ExceptTweil ich gerne an die Möglichkeit eines Scheiterns erinnert werde.


Zusammenfassend

  • Maybe - Ich kann auf eine offensichtliche Weise scheitern
  • Either CustomType - Ich kann scheitern, und ich sage Ihnen, was passiert ist
  • IO+ Ausnahmen - Ich scheitere manchmal. Überprüfen Sie meine Dokumente, um zu sehen, was ich bei der Arbeit werfe
  • error - Ich hasse dich auch User
Daniel Gratzer
quelle
Diese Antwort klärt mich sehr auf. Ich habe mich selbst nachempfunden, weil die eingebauten Funktionen so gut headoder lastso gut zu sein scheinen. errorIch habe mich gefragt, ob das wirklich eine gute Möglichkeit ist, Dinge zu tun, und ich habe einfach etwas verpasst. Dies beantwortet diese Frage. :)
CmdrMoozy
5
@CmdrMoozy Bin froh zu helfen :) Es ist wirklich bedauerlich, dass das Vorspiel so schlechte Praktiken hat.
Daniel Gratzer
3
+1 Ihre Zusammenfassung ist erstaunlich
recursion.ninja
2

Ich würde 2 und 3 nicht danach trennen, wie oft etwas versagen kann. Sobald Sie denken, dass es nur einen Weg gibt, wird ein anderer sein Gesicht zeigen. Die Metrik sollte stattdessen lauten: "Interessiert es meine Anrufer, warum etwas fehlgeschlagen ist? Können sie tatsächlich etwas dagegen unternehmen?".

Darüber hinaus ist mir nicht sofort klar, dass Either String SomeTypeeine Fehlerbedingung vorliegen kann. Ich würde einen einfachen algebraischen Datentyp mit den Bedingungen mit einem aussagekräftigeren Namen erstellen.

Welche Sie verwenden, hängt von der Art des Problems und den Redewendungen des Softwarepakets ab, mit dem Sie arbeiten. Obwohl ich dazu neige, # 1 zu vermeiden.

Telastyn
quelle
Tatsächlich ist das Verwenden Eitherfür Fehler in Haskell ein sehr bekanntes Muster.
Rufflewind
1
@ Rufflewind - Eitherist nicht der unklare Teil, die Verwendung Stringals Fehler und nicht als tatsächliche Zeichenfolge.
Telastyn
Ah, ich habe deine Absicht falsch verstanden.
Rufflewind