~/ / docs / Fortgeschritten / 06 · Fortgeschritten
Fehler mit Result und Option
Warum Option und Result Exceptions in OCaml-Code meist überflüssig machen — und wie man Fehlerketten lesbar aneinanderreiht.
OCaml kennt Exceptions, doch im typischen Anwendungscode dominiert ein anderer Stil: Fehler werden als Werte zurückgegeben. Zwei Typen tragen diese Idee — option für fehlende Werte, result für unterscheidbare Fehler.
option für „möglicherweise nichts”
option ist eine einfache Variante mit zwei Konstruktoren — Some enthält einen Wert, None steht für dessen Abwesenheit:
let lookup_age people name =
List.assoc_opt name people
(* val lookup_age : (string * int) list -> string -> int option *)
Aufrufer müssen den Fall ausdrücklich behandeln — der Compiler zwingt sie dazu:
match lookup_age db "Anna" with
| Some age -> Printf.printf "Alter: %d\n" age
| None -> print_endline "unbekannt"
option eignet sich, wenn ein Fehler keine weitere Erklärung braucht: gefunden oder nicht, vorhanden oder nicht.
result für aussagekräftige Fehler
Sobald man wissen will warum etwas schiefging, kommt result ins Spiel — eine Variante mit zwei Konstruktoren Ok und Error:
type ('a, 'e) result = Ok of 'a | Error of 'e
Ein typisches Beispiel: Eine Datei einlesen kann mehrere Fehlerursachen haben.
type read_error =
| Not_found
| Permission_denied
| Io_error of string
let read_file path : (string, read_error) result =
try Ok (In_channel.with_open_text path In_channel.input_all)
with
| Sys_error msg when String.length msg > 0 -> Error (Io_error msg)
| _ -> Error Not_found
Der Aufrufer matcht auf das Ergebnis und entscheidet, wie zu reagieren ist:
match read_file "config.toml" with
| Ok content -> parse content
| Error Not_found -> use_defaults ()
| Error (Io_error msg) -> Printf.eprintf "IO: %s\n" msg; exit 1
| Error Permission_denied -> Printf.eprintf "Keine Rechte.\n"; exit 1
Verkettung mit bind
Bei mehreren Schritten, die alle scheitern können, wird das verschachtelte match schnell unübersichtlich. Hier hilft Result.bind (oder der Infix-Operator ( let* ) aus Standardlibs):
let ( let* ) = Result.bind
let pipeline path =
let* raw = read_file path in
let* parsed = parse raw in
let* validated = validate parsed in
Ok validated
Jeder Schritt entpackt automatisch das Ok und propagiert ein Error direkt ans Ende. Das nennt sich monadische Schreibweise und ist heute der Standardstil für Fehlerketten.
Auf der Shell zum Testen:
dune utop
utop # pipeline "data.json" ;;
- : (config, read_error) result = Ok { ... }
option und result machen Fehlerpfade explizit — sie tauchen im Typ auf, der Compiler hilft beim Behandeln. Im nächsten Kapitel geht es um dune, das Build-Tool, das hinter den meisten OCaml-Projekten steckt.