私は型理論家でも正式な意味論者でもありませんが、運用上の観点から定義が何を達成しようとしているのかを理解していると思います。
ML 例外が生成的であるということは、フローの制御が同じ例外宣言に 2 回到達するたびに、2 つの異なる例外が作成されることを意味します。これらのオブジェクトはメモリ内にあるだけでなく、拡張的にも等しくありません。これらのオブジェクトは、例外コンストラクターに対するパターン マッチングによって区別できます。
[ちなみに、これは ML 例外と他のほとんどの言語の例外との重要な違いを示しています。ML では、実行時に新しい例外クラスを作成できます。]
一方、プログラムが同じ整数のリストを 2 回作成する場合、メモリ内に 2 つの異なるオブジェクトが存在する可能性がありますが、プログラムにはそれらを区別する方法がありません。それらは拡張的に等しいです。
生成例外が役立つ理由の例として、MLton のユニバーサル タイプの実装例を考えてみましょう。
signature UNIV =
sig
type univ
val embed : unit -> { inject : 'a -> univ
, project : univ -> 'a option
}
end
structure Univ :> UNIV =
struct
type univ = exn
fun 'a embed () =
let
exception E of 'a
in
{ inject = E
, project = fn (E x) => SOME x | _ => NONE
}
end
end
ML に値の制限がない場合、このコードは巨大なタイプ セーフティ ホールを引き起こします。
val { inject = inj1, project = proj1 } = Univ.embed ()
val { inject = inj2, project = proj2 } = Univ.embed ()
(* `inj1` and `proj1` share the same internal exception. This is
* why `proj1` can project values injected with `inj1`.
*
* `inj2` and `proj2` similarly share the same internal exception.
* But this exception is different from the one used by `inj1` and
* `proj1`.
*
* Furthermore, the value restriction makes all of these functions
* monomorphic. However, at this point, we don't know yet what these
* monomorphic types might be.
*)
val univ1 = inj1 "hello"
val univ2 = inj2 5
(* Now we do know:
*
* inj1 : string -> Univ.univ
* proj1 : Univ.univ -> string option
* inj2 : int -> Univ.univ
* proj2 : Univ.univ -> int option
*)
val NONE = proj1 univ2
val NONE = proj2 univ1
(* Which confirms that exceptions are generative. *)
val SOME str = proj1 univ1
val SOME int = proj2 univ2
(* Without the value restriction, `str` and `int` would both
* have type `'a`, which is obviously unsound. Thanks to the
* value restriction, they have types `string` and `int`,
* respectively.
*)