You can locally define exceptions that refer to polymorphic type variables, and you can raise and catch them locally. For example:
fun 'a f(x : 'a) =
let
exception E of 'a
fun g() = raise E x
fun h() = g() handle E y => y
in
h()
end
Note that this is not a polymorphic exception, though -- it is monomorphic relative to the type 'a
in scope, and you can only apply it to values of that type, i.e., only x
.
Consequently, there is no way to define such an exception globally, because no type variables can exist in the global scope (where should they be bound or instantiated?).
You cannot have truly polymorphic exceptions in SML. In principle, allowing this would be possible via existential quantification, but it would not be very useful in practice. Since there would be no way of knowing the type when matching an exception, the type would have to be treated as fully abstract. For example:
exception E of 'a (* hypothetical existential exception *)
fun f1() = raise E 1
fun f2() = raise E "foo"
fun g f = f() handle E x => () (* type of x is abstract here *)
The only marginally useful example would be something like
exception E of ('a -> int) * 'a
fun f1() = raise E(fn x => x, 1)
fun f2() = raise E(String.size, "foo")
fun g f = f() handle E(h, x) => h x
But there is little reason not to replace this with a simpler version that does not require existential types:
exception E of unit -> int
fun f1() = raise E(fn() => 1)
fun f2() = raise E(fn() => String.size "foo")
fun g f = f() handle E h => h()
In practice, nobody probably wants to pass around a first-class ADT in an exception...