Exceptions
raise, handle, exception bindings
Exceptions are SML's mechanism for non-local control transfer. They are first-class values of type exn that can be raised (like throw) and handled (like catch).
Per Def 5.4 (notes p.115): an exception is a special SML object. Raising an exception in a function aborts computation and returns the exception to the next enclosing handler.
Declaring an exception:exception Empty;exception Factorial;exception SysError of int;
The third form creates an exception constructor that carries an int payload.
Raising:raise Factorial — aborts the current computation and propagates upward.
Handling — the handle clause (Def 5.10):
expr handle Empty => 0
| SysError n => ~1
| _ => 999;The handle clause attaches a list of rules to an expression. If expr raises an exception, SML tries each rule top-to-bottom and uses the FIRST one that matches.
Order of handlers matters. If you write _ => 0 first, every exception will match it; the more specific rules below are dead code.
Common pattern — local exception + safe wrapper:
local
exception Negative
fun fact 0 = 1 | fact n = n * fact (n - 1)
in
fun safe_factorial n = if n < 0 then raise Negative else fact n
end;The wrapper checks the precondition once; the local fact trusts its argument. This is the recommended pattern from the slides.
- 0.0sExceptions: raise and handle
- 1.4sA cyan call box: f x
- 2.5sraise Foo text appears with an amber bolt
- 4.3sBolt moves down toward the green handle block
- 6.3sHandle box draws, handle header appears
- 7.0sBolt travels into the handle region
- 7.7sPattern Foo implies fallback flashes amber
- 9.0sResult label: fallback
- 10.5sraise transfers control to the nearest matching handle
An effect is an action during computation that does not return a value but changes something external. Effects include:
- I/O: reading from / writing to the outside world (files, network, terminal).
- Mutation: changing a mutable variable or array element.
- Communication: sending messages between processes.
- Exceptions: non-local transfer of control (covered separately).
- Logging: writing to an audit trail.
A pure function has no effects. In SML, effects are mediated by special types:
- 'a ref for mutable cells.
- TextIO.instream / TextIO.outstream for I/O streams.
- exn for exceptions.
Documentation convention: write the signature as f : arg → value ! effects where ! effects lists side effects.
SML provides the TextIO structure for character-based I/O on text streams:
val stdin : instream (* default input stream *)
val stdout : outstream (* default output stream *)
val inputLine : instream -> string option
val print : string -> unit
val openIn : string -> instream
val openOut : string -> outstream
val closeIn : instream -> unit
val closeOut : outstream -> unit
val output1 : outstream -> char -> unit
val input1 : instream -> char option
inputLine returns SOME s if a line was read, or NONE at end-of-file. input1 reads one character (returning NONE at EOF). The 'a option type is the SML idiom for partial functions.
The function Int.fromString : string -> int option parses a string as an integer, returning NONE if parsing fails. A read_integer function:
exception NaN;
fun read_integer () =
let
val intstring = case TextIO.inputLine TextIO.stdIn of
NONE => raise NaN
| SOME s => s
in
case Int.fromString intstring of
NONE => raise NaN
| SOME i => i
end;
Used in a factorial_driver:
fun factorial_driver () =
let val input = read_integer ()
val result = Int.toString (safe_factorial input)
in
TextIO.print (result ^ "\n")
end
handle Factorial => TextIO.print "Out of range.\n"
| NaN => TextIO.print "Not a Number!\n";
For tree ADTs, exceptions are used to signal error conditions like 'position out of bounds'.
Example (assignment 6.9):
datatype btree = Empty
| Node of int * btree * btree;
datatype position = Here
| Left of position
| Right of position;
exception TreeExceeded;
fun getSubtree p Empty = raise TreeExceeded
| getSubtree Here (Node (_, l, _)) = l
| getSubtree Here (Node (_, _, r)) = r
| getSubtree (Left p) (Node (_, l, r)) = getSubtree p l
| getSubtree (Right p) (Node (_, l, r)) = getSubtree p r
| getSubtree _ Empty = raise TreeExceeded;
Trace. getSubtree (Left Here) (Node (1, Node (2, Empty, Empty), Empty)):
- Match Left p with . Recurse into left subtree.
- Match Here against . Return .
Reading. A position is a path in the tree, from the root: Here = 'at this node', Left p = 'go left then follow p', Right p = 'go right then follow p'. If the path leads off the tree (or to an Empty), raise TreeExceeded.
Used in assignment 6.9. The complete program combines getSubtree, cutSubtree, and a print driver.
raise do?expr handle rules do (Def 5.10)?exception SysError of int, what kind of exception is this?TextIO.inputLine TextIO.stdIn returns:getSubtree Here of any Node returns:exception Empty; creates a(n) ___ exception (one without payload).Int.fromString "42" returns SOME ___.getSubtree raises ___ if the position leads off the tree.