Standard ML
exceptions
exceptions - motivation
- An extensive part of code is error handling
- A function may return an answer, fail to find one or signal that a solution does not exists
exceptions - alternative
datatype int_sol = Success of int | Failure | Impossible;
case methodA problem of
Success s => Int.toString s
| Failure => (case methodB problem of
Success s => Int.toString s
| Failure => "Both failed"
| Impossible => "No Good")
| Impossible => "No Good"
;
Without exceptions, error handling can be tedious and requires explicit handling.
Sometimes we don’t really know what to do with the error, so we’ll simply return it
exceptions usage - key concepts
- When an error is discovered we will raise an exception
- The exception will propagate up until someone handles it
The caller of a function doesn’t have to check any error values
exceptions usage
In pseudo code:
fun inner = do_calculation
if local_error then raise local_error,
if global_error then raise global_error;
fun middle = inner (…) handle local_error;
fun outer = middle (…) handle global_error;
the exception type exn
- In SML we can raise only values of specific type:
exn
exn
is a special datatype with an extendable set of constructors and values
exception Failure;
Failure;
exception Problem of int;
Problem;
the exception type exn
Values of type exn
have all the privileges of other values …
val p = Problem 1;
map Problem [0, 1, 2];
fun whats_the_problem (Problem p) = p;
the exception type exn
… except
val x = Failure;
x = x;
raising exceptions
raise Exp
- Assume the expression
Exp
evaluates toe
(and is of typeexn
) raise Exp
evaluates to an exception packet containinge
Important note - packets are not ML values!
raising exceptions
All of the following “evaluate” to raise Exp
f (raise Exp)
(raise Exp) arg
raise (Exp1 (raise Exp)) (* Exp1 is a constructor *)
(raise Exp, raise Exp2) (* or {a=raise Exp, b=raise Exp2} *)
let val name = raise Exp in some_expression end
local val name = raise Exp in some_declaration end
fixing hd
and tl
exception Empty;
fun hd (x :: _) = x
| hd [] = raise Empty;
fun tl (_ :: xs) = xs
| tl [] = raise Empty;
handling exceptions - syntax
Exp_0 handle
P1 => Exp_1
| ...
| Pn => Exp_n
- All
Exp_i
s must be type-compatible - All
Pi
s must be valid patterns for the typeexn
fun len l = 1 + len (tl l) handle Empty => 0;
handling exceptions - semantics
Exp_0 handle Cons1 x => Exp_1
- Assume
Exp_0
evaluates to someV
(which is either a value or an exception packet), then the expression evaluates to:Exp_1
- in caseV
israise Cons1 x
V
- otherwise (V
may be either a normal value or a non-matching raised exception)
handle
is short-circuiting
Exactly equivalent to familiar notions from C++
the type of raise Exp
- The expression
raise Exp
is of type'a
- It is not an expression of type
exn
- This is necessary to avoid restricting the other parts of the expression.
fun throw _ = raise Empty;
exception Underflow;
fun bar x = if x > 0 then x else raise Underflow;
using exception handling
case methodA problem of
Success s => Int.toString s
| Failure => (case methodB problem of
Success s => Int.toString s
| Failure => "Both failed"
| Impossible => "No Good")
| Impossible => "No Good"
and now with exceptions:
toString (methodA problem handle Failure => methodB problem)
handle Failure => "Both failed"
| Impossible => "No Good"