Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | GenerativeException |
2 | =================== | |
3 | ||
4 | In <:StandardML:Standard ML>, exception declarations are said to be | |
5 | _generative_, because each time an exception declaration is evaluated, | |
6 | it yields a new exception. | |
7 | ||
8 | The following program demonstrates the generativity of exceptions. | |
9 | ||
10 | [source,sml] | |
11 | ---- | |
12 | exception E | |
13 | val e1 = E | |
14 | fun isE1 (e: exn): bool = | |
15 | case e of | |
16 | E => true | |
17 | | _ => false | |
18 | exception E | |
19 | val e2 = E | |
20 | fun isE2 (e: exn): bool = | |
21 | case e of | |
22 | E => true | |
23 | | _ => false | |
24 | fun pb (b: bool): unit = | |
25 | print (concat [Bool.toString b, "\n"]) | |
26 | val () = (pb (isE1 e1) | |
27 | ;pb (isE1 e2) | |
28 | ; pb (isE2 e1) | |
29 | ; pb (isE2 e2)) | |
30 | ---- | |
31 | ||
32 | In the above program, two different exception declarations declare an | |
33 | exception `E` and a corresponding function that returns `true` only on | |
34 | that exception. Although declared by syntactically identical | |
35 | exception declarations, `e1` and `e2` are different exceptions. The | |
36 | program, when run, prints `true`, `false`, `false`, `true`. | |
37 | ||
38 | A slight modification of the above program shows that even a single | |
39 | exception declaration yields a new exception each time it is | |
40 | evaluated. | |
41 | ||
42 | [source,sml] | |
43 | ---- | |
44 | fun f (): exn * (exn -> bool) = | |
45 | let | |
46 | exception E | |
47 | in | |
48 | (E, fn E => true | _ => false) | |
49 | end | |
50 | val (e1, isE1) = f () | |
51 | val (e2, isE2) = f () | |
52 | fun pb (b: bool): unit = | |
53 | print (concat [Bool.toString b, "\n"]) | |
54 | val () = (pb (isE1 e1) | |
55 | ; pb (isE1 e2) | |
56 | ; pb (isE2 e1) | |
57 | ; pb (isE2 e2)) | |
58 | ---- | |
59 | ||
60 | Each call to `f` yields a new exception and a function that returns | |
61 | `true` only on that exception. The program, when run, prints `true`, | |
62 | `false`, `false`, `true`. | |
63 | ||
64 | ||
65 | == Type Safety == | |
66 | ||
67 | Exception generativity is required for type safety. Consider the | |
68 | following valid SML program. | |
69 | ||
70 | [source,sml] | |
71 | ---- | |
72 | fun f (): ('a -> exn) * (exn -> 'a) = | |
73 | let | |
74 | exception E of 'a | |
75 | in | |
76 | (E, fn E x => x | _ => raise Fail "f") | |
77 | end | |
78 | fun cast (a: 'a): 'b = | |
79 | let | |
80 | val (make: 'a -> exn, _) = f () | |
81 | val (_, get: exn -> 'b) = f () | |
82 | in | |
83 | get (make a) | |
84 | end | |
85 | val _ = ((cast 13): int -> int) 14 | |
86 | ---- | |
87 | ||
88 | If exceptions weren't generative, then each call `f ()` would yield | |
89 | the same exception constructor `E`. Then, our `cast` function could | |
90 | use `make: 'a -> exn` to convert any value into an exception and then | |
91 | `get: exn -> 'b` to convert that exception to a value of arbitrary | |
92 | type. If `cast` worked, then we could cast an integer as a function | |
93 | and apply. Of course, because of generative exceptions, this program | |
94 | raises `Fail "f"`. | |
95 | ||
96 | ||
97 | == Applications == | |
98 | ||
99 | The `exn` type is effectively a <:UniversalType:universal type>. | |
100 | ||
101 | ||
102 | == Also see == | |
103 | ||
104 | * <:GenerativeDatatype:> |