Fachkonzept - Fehlerbehandlung mit Result
Fehlerarten als Daten
In der Programmiersprache Elm werden Fehler als Daten behandelt.
Für einfache Fehlersituationen, bei denen es nur darum geht, ob eine Berechnung erfolgreich war oder nicht, ist der Datentyp Maybe geeignet.
Für komplexere Fehlersituationen, bei denen es darum geht, unterschiedliche Fehlerarten zu unterscheiden, ist der Datentyp Result geeignet.
Der Datentyp Result
Der Datentyp Result ist so definiert:
type Result error value
= Ok value
| Err error
Die Typvariablen error und value können dabei
mit beliebigen vordefinierten Typen oder selbst definierten Typen konkretisiert werden.
Die Typvariable error steht dabei für die Fehlerart, die
in einer Fehlersituation vorliegt.
Die Typvariable value steht dabei für den Datentyp, der bei
einer erfolgreichen Berechnung vorliegt.
Beispiel: Result String Int
Wenn man Result mit den Typen String
und Int konkretisiert, dann erhält im Fehlerfall
einen Wert wie z.B. Err "Fehler: ungültige Zahl",
der die Fehlersituation beschreibt, und im Erfolgsfall einen Wert
wie z.B. Ok 2, der die Ergebniszahl enthält.
Man könnte z.B. eine Funktion teilePositiv definieren,
die nur für positive Zahlen definiert sein soll.
Im Fehlerfall bekommt man einen Wert zurück, der die Fehlersituation beschreibt.
Im Erfolgsfall bekommt man einen Wert zurück, der das Ergebnis der Berechnung enthält.
teilePositiv : Int -> Int -> Result String Int
teilePositiv zaehler nenner =
if nenner == 0 then
Err "Fehler: Division durch Null"
else if zaehler < 0 || nenner < 0 then
Err "Fehler: negative Zahl"
else
Ok (zaehler // nenner)
Die Funktion wird dann wie folgt benutzt:
> teilePositiv 10 2
Ok 5 : Result String Int
> teilePositiv 10 0
Err "Fehler: Division durch Null" : Result String Int
> teilePositiv (-10) 2
Err "Fehler: negative Zahl" : Result String Int
Noch besser wäre es, wenn man die Fehlersituationen mit Hilfe von selbst definierten Datentypen beschreibt, damit die verschiedenen Fehlersituationen auch über die Fehlermeldungen hinaus klar voneinander zu unterscheiden sind.
type Fehlerart
= DivisionDurchNull
| NegativeZahl
Dann könnte man die Funktion teilePositiv wie folgt definieren:
teilePositiv : Int -> Int -> Result Fehlerart Int
teilePositiv zaehler nenner =
if nenner == 0 then
Err DivisionDurchNull
else if zaehler < 0 || nenner < 0 then
Err NegativeZahl
else
Ok (zaehler // nenner)
Die Funktion wird dann wie folgt benutzt:
> teilePositiv 10 2
Ok 5 : Result Fehlerart Int
> teilePositiv 10 0
Err DivisionDurchNull : Result Fehlerart Int
> teilePositiv (-10) 2
Err NegativeZahl : Result Fehlerart Int
Verarbeitung von Result-Werten
Auf die Werte Werte eines Result-Typs kann man mit
Hilfe von Pattern Matching zugreifen:
divisionToString : Int -> Int -> String
divisionToString zaehler nenner =
case teilePositiv zaehler nenner of
Ok ergebnis ->
"Ergebnis: " ++ String.fromInt ergebnis
Err DivisionDurchNull ->
"Fehler: Division durch Null"
Err NegativeZahl ->
"Fehler: negative Zahl"
Falls man die Fehlerfälle nicht unterscheiden möchte, kann man den konkreten Fehlerfall auch mit einem Wildcard-Pattern ignorieren:
divisionToString : Int -> Int -> String
divisionToString zaehler nenner =
case teilePositiv zaehler nenner of
Ok ergebnis ->
"Ergebnis: " ++ String.fromInt ergebnis
Err _ ->
"Fehler: ungültige Eingabe"