In Standard ML, exception declarations are said to be generative, because each time an exception declaration is evaluated, it yields a new exception.

The following program demonstrates the generativity of exceptions.

exception E
val e1 = E
fun isE1 (e: exn): bool =
   case e of
      E => true
    | _ => false
exception E
val e2 = E
fun isE2 (e: exn): bool =
   case e of
      E => true
    | _ => false
fun pb (b: bool): unit =
   print (concat [Bool.toString b, "\n"])
val () = (pb (isE1 e1)
          ;pb (isE1 e2)
          ; pb (isE2 e1)
          ; pb (isE2 e2))

In the above program, two different exception declarations declare an exception E and a corresponding function that returns true only on that exception. Although declared by syntactically identical exception declarations, e1 and e2 are different exceptions. The program, when run, prints true, false, false, true.

A slight modification of the above program shows that even a single exception declaration yields a new exception each time it is evaluated.

fun f (): exn * (exn -> bool) =
   let
      exception E
   in
      (E, fn E => true | _ => false)
   end
val (e1, isE1) = f ()
val (e2, isE2) = f ()
fun pb (b: bool): unit =
   print (concat [Bool.toString b, "\n"])
val () = (pb (isE1 e1)
          ; pb (isE1 e2)
          ; pb (isE2 e1)
          ; pb (isE2 e2))

Each call to f yields a new exception and a function that returns true only on that exception. The program, when run, prints true, false, false, true.

Type Safety

Exception generativity is required for type safety. Consider the following valid SML program.

fun f (): ('a -> exn) * (exn -> 'a) =
   let
      exception E of 'a
   in
      (E, fn E x => x | _ => raise Fail "f")
   end
fun cast (a: 'a): 'b =
   let
      val (make: 'a -> exn, _) = f ()
      val (_, get: exn -> 'b) = f ()
   in
      get (make a)
   end
val _ = ((cast 13): int -> int) 14

If exceptions weren’t generative, then each call f () would yield the same exception constructor E. Then, our cast function could use make: 'a -> exn to convert any value into an exception and then get: exn -> 'b to convert that exception to a value of arbitrary type. If cast worked, then we could cast an integer as a function and apply. Of course, because of generative exceptions, this program raises Fail "f".

Applications

The exn type is effectively a universal type.

Also see