3.6 Ausnahmebehandlung

Ausnahmebehandlung unterstützt die Programmierung partieller Funktionen. Das Problem ist, sicherzustellen, daß auch im Falle des Aufrufs einer solchen Funktion mit einem Parameter außerhalb des Definitionsbereichs eine wohldefinierte, typsichere Berechnung erfolgt.

- exception Head;
exception Head
- fun head nil = raise Head
=  | head (x::l) = x;
val head = fn : 'a list -> 'a
- head nil;
uncaught exception Head

Ausnahmeindikatoren, wie Head, werden durch eine Deklaration der Form

                      exception <Id> ≪ of <Typ> ≫ ^{1}

eingeführt. Will man im Ausnahmefall neben dem Indikator <Id> auch noch einen Wert vom Typ <Typ> mitgeben, so verwdndet man die erweiterte Variante der Deklaration. Bei Auswertung von Durch

                        raise <Id> ≪ <Ausdruck> ≫

wird eine Ausnahmewert von der Form <Id> bzw. <Id>(x) erzeugt, wenn x der Wert des Ausdrucks <Ausdruck> ist.

- head nil;
uncaught exception Head

Der Effekt ist, falls keine andere Behandlung definiert ist, daß die Auswertung mit einer entsprechenden Fehlermeldung abgebrochen wird. Eine Ausnahmebehandlung kann explizit programmiert werden. Die zugehörige Konstruktion hat die Form

e handle M_{1} => e_{1} | … | M_{k} => e_{k}

mit Ausdrücken e und e_{i} sowie Mustern M_{i}. Deren Auswertung ist wie folgt definiert: (i) Werte e aus; ergibt sich ein Wert, so ist dies der Wert der gesamten Konstruktion. (ii) Tritt eine Ausnahmesituation mit einem Ausnahmewert a ein, der auf eines der

Muster paßt, so nehme das erste unter den passenden Mustern, sagen wir M_{i}, erstelle die durch die Anpaßsubstitution gegebenen Bindungen für die Variablen in M_{i} und werte den zugehörigen Ausdruck M_{i} aus. Das Ergebnis dieser Auswertung ist das Ergebnis der gesamten Konstruktion.

(iii) Andernfalls liefere den Ausnahmewert a als Ergebnis ab, der dann vielleicht in einer äußeren Ausnahmebehandlung behandelt wird.

1Mit ≪ unf ≫ kennzeichnen wir optionale Syntax.

- fun head2 l = head(l) handle Head => 0;
val head2 = fn : int list -> int
- head2 [2,3,4];
val it = 2 : int
- head2 [];
val it = 0 : int

In obigem Beispiel sieht man die Typsicherheit dieser Konstruktion. Während head den polymorphen Typ τ list → τ hat, hat head2 wegen der speziellen Ausnahmebehandlung den Typ int list → int.

In Ausnahmeindikatoren sollte man nicht nur eine Notation für spezielle Fehlerwerte sehen, sondern ein Mittel, daß es dem Programmierer gestattet, auf das explizite Einpacken von Werten zu verzichten — eine Technik, mit der man ansonsten Fehlerbehandlung programmieren würde. Hierbei würde man für eine partielle Funktion f : t′ → t zunächst einen neuen Typ

                  datatype tt = success of t | failure

definieren müssen und dann, wenn f(x) definiert ist, success(x) zurückliefern, andernfalls failure. Dies bedeutet aber, daß bei jeder gewünschten Komposition von f mit einer Funktion g : t → t̃, etwas in einem Ausdruck der Form g(f(x)), zunächst mal die Typisierung nicht paßt. Also muß g auf den Typ tt erweitert werden, was bedeutet, daß im Kode für g zunächst das Ergebnis von f ausgepackt werden muß, bevor es an die eigentliche Berechnung von g übergeben werden kann. failure müßte in irgendeinen ”speziellen“ Wert aus t kodiert werden, oder aber es müßte auch der Ergebnistyp von g entsprechend um Fehlerwerte erweitert werden, selbst dann, wenn g eine totale Funktion ist. Möchte man g nicht ändern, muß man an jeder Aufrufstelle von f eine Spezialbehandlung der Fehlersituation vornehmen. In SML schreibt man einfacher raise Ex, wenn f(x) undefiniert ist, und kann sich eine geeignete Fortsetzung für f an der Stelle x, sagen wir zu x′, aus dem Aufrufkontext durch Ausnahmebehandlung f(x) handle Ex => x′ konstruieren. In jedem Fall ist aber g (f x), auch bei Nichtbehandlung der möglichen Fehlersituation, ein sinnvoller Ausdruck, bei dem jeder eventuell auftretende Ausnahmewert einfach automatisch soweit nach außen propagiert wird, bis eine Ausnahmebehandlung definiert ist. Wir werden nichttriviale Verwendungen dieses Mechanismus in Abschnitt ?? bei der Programmierung eines Simplifikators für arithmetische Ausdrücke kennenlernen.