(* 15-212, Spring 2011 *)
(* Michael Erdmann *)
(* Code for Lecture 13: Exceptions. *)
(************************************************************************)
(* Exceptions:
Declaring
Raising
Handling
*)
(************************************************************************)
val almostinfinity = 99999999999.9;
(* Simple declaration: *)
exception Divide;
(* Raising an exception, as we've seen before: *)
(* val divide : real*real->real
divide(r1,r2) ==> r1/r2
Invariants: none
Effects: will raise Divide if r2 is 0.0
*)
fun divide(r1, r2) =
if Real.==(r2, 0.0) then raise Divide
else r1/r2;
(* Declaration with an argument: *)
exception Rdivide of real;
(* Raising an exception with an argument: *)
(* val rdivide : real*real->real
rdivide(r1,r2) ==> r1/r2
Invariants: none
Effects: will raise Rdivide(r1) if r2 is 0.0
*)
fun rdivide(r1, r2) =
if Real.==(r2, 0.0) then raise Rdivide(r1)
else r1/r2;
(* Why is this useful?
Well, now we can pass back values in the exception.
This is particularly useful if we handle the exception:
*)
rdivide(1.0, 0.0)
handle Rdivide(r) => (print ("***ERROR: You tried to divide "^
(Real.toString r)^
" by zero!\n");
r*almostinfinity);
(************************************************************************)
(* *)
(* n-Queens, using Exceptions *)
(* *)
(************************************************************************)
(* val threat : (int*int) -> (int*int) -> bool
(threat p1 p2) ==> true iff position p1 is threatened
by the queen at position p2.
No invariants or effects.
*)
fun threat (x, y) (x',y') =
(x=x') orelse (y=y') orelse (x+y = x'+y') orelse (x-y = x'-y');
(* val conflict : (int*int) -> (int*int) list -> bool
(conflict p plist) ==> true iff position p is threatened by any of
the queens at positions plist.
No invariants or effects.
*)
fun conflict pos = List.exists (threat pos);
exception Conflict;
(* val addqueen : int * int * (int * int) list -> (int * int) list
addqueen(i, n, qlist) places queens i, ..., n on an n x n board,
given that queens 1, ..., i-1 have already
been placed at positions qlist, such that no
queen threatens another. If this is not
possible, addqueen raises exception Conflict.
invariant: qlist is of the form
[(i-1, _), (i-2, _), ... (1, _)] or nil,
where each _ refers to some integer in the range [1, n],
describing conflict-free placements of the first i-1
queens.
val try : int -> (int * int) list
try(j) places the ith queen at position (i, j) if this is possible
without a conflict. Otherwise, try raises exception Conflict.
Effects: may raise Conflict as described above.
*)
fun addqueen(i, n, qlist) =
let fun try j =
(if (conflict (i,j) qlist) then raise Conflict
else if i=n then (i,j)::qlist
else addqueen(i+1, n, (i,j)::qlist))
handle Conflict => (if j=n then raise Conflict
else try(j+1))
in
try 1
end;
(* val queens : int -> (int * int) list
queens(n) places n queens on an n x n board;
will raise Conflict if this is not possible.
Invariants: n >= 1.
Effects: may raise Conflict.
*)
fun queens(n) = addqueen(1, n, nil);
queens 4;
queens 8;
(************************************************************************
For comparison here is an implementation of n-Queens using options
************************************************************************)
(* val addqueen : int * int * (int * int) list -> (int * int) list option
val try : int -> (int * int) list option
The specs for the functions "addqueen", "try", and "queens" are
pretty much as before, except that we now return an option. We use
the difference between SOME and NONE to control computation flow.
NONE plays the role that raising the exception Conflict did before.
Effects: none
*)
fun addqueen(i, n, qlist) =
let fun try j=
(case (if (conflict (i,j) qlist) then NONE
else if i=n then SOME((i,j)::qlist)
else addqueen(i+1, n, (i,j)::qlist))
of NONE => if (j=n) then NONE else try(j+1)
| placements => placements)
in
try 1
end;
(* val queens : int -> (int * int) list option *)
fun queens(n) = addqueen(1, n, nil);
queens 4;
queens 8;
(************************************************************************
For further comparison here is an implementation of n-Queens
using failure continuations.
************************************************************************)
(* val addqueen:
int * int * (int * int) list * (unit -> (int * int) list option)
-> (int * int) list option
val try : int * -> (int * int) list option
The specs for the functions "addqueen", "try", and "queens" are
similar to the previous two versions. Again, we return an option
as in the second version above.
Control flow is now handled using continuations. In other words,
rather than raise an exception or signal failure with NONE, addqueen
now expects a failure continuation. This continuation specifies
what to do in the event that a queen cannot be placed.
The failure continuation takes unit as an argument and, when called,
effectively implements the backtracking. Observe that each new
call to addqueen creates a new failure continuation; the lexical
scoping rules ensure that calling this continuation is the same as
backtracking, i.e., continuing the search from the place the
continuation was defined.
Effects: none
*)
fun addqueen(i, n, qlist, fc) =
let fun try j =
if j=n+1 then fc()
else if (conflict (i,j) qlist) then try(j+1)
else if i=n then SOME((i,j)::qlist)
else addqueen(i+1, n, (i,j)::qlist, fn () => try(j+1))
in
try 1
end;
(* val queens : int -> (int * int) list option *)
fun queens(n) = addqueen(1, n, nil, fn () => NONE);
queens 4;
queens 8;
(************************************************************************)