## MLton 20241230

# GenerativeException

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

- GenerativeDatatype