Import Upstream version 20180207
[hcoop/debian/mlton.git] / doc / guide / src / ValueRestriction.adoc
CommitLineData
7f918cf1
CE
1ValueRestriction
2================
3:toc:
4
5The value restriction is a rule that governs when type inference is
6allowed to polymorphically generalize a value declaration. In short,
7the value restriction says that generalization can only occur if the
8right-hand side of an expression is syntactically a value. For
9example, in
10
11[source,sml]
12----
13val f = fn x => x
14val _ = (f "foo"; f 13)
15----
16
17the expression `fn x => x` is syntactically a value, so `f` has
18polymorphic type `'a -> 'a` and both calls to `f` type check. On the
19other hand, in
20
21[source,sml]
22----
23val f = let in fn x => x end
24val _ = (f "foo"; f 13)
25----
26
27the expression `let in fn x => end end` is not syntactically a value
28and so `f` can either have type `int -> int` or `string -> string`,
29but not `'a -> 'a`. Hence, the program does not type check.
30
31<:DefinitionOfStandardML:The Definition of Standard ML> spells out
32precisely which expressions are syntactic values (it refers to such
33expressions as _non-expansive_). An expression is a value if it is of
34one of the following forms.
35
36* a constant (`13`, `"foo"`, `13.0`, ...)
37* a variable (`x`, `y`, ...)
38* a function (`fn x => e`)
39* the application of a constructor other than `ref` to a value (`Foo v`)
40* a type constrained value (`v: t`)
41* a tuple in which each field is a value `(v1, v2, ...)`
42* a record in which each field is a value `{l1 = v1, l2 = v2, ...}`
43* a list in which each element is a value `[v1, v2, ...]`
44
45
46== Why the value restriction exists ==
47
48The value restriction prevents a ref cell (or an array) from holding
49values of different types, which would allow a value of one type to be
50cast to another and hence would break type safety. If the restriction
51were not in place, the following program would type check.
52
53[source,sml]
54----
55val r: 'a option ref = ref NONE
56val r1: string option ref = r
57val r2: int option ref = r
58val () = r1 := SOME "foo"
59val v: int = valOf (!r2)
60----
61
62The first line violates the value restriction because `ref NONE` is
63not a value. All other lines are type correct. By its last line, the
64program has cast the string `"foo"` to an integer. This breaks type
65safety, because now we can add a string to an integer with an
66expression like `v + 13`. We could even be more devious, by adding
67the following two lines, which allow us to threat the string `"foo"`
68as a function.
69
70[source,sml]
71----
72val r3: (int -> int) option ref = r
73val v: int -> int = valOf (!r3)
74----
75
76Eliminating the explicit `ref` does nothing to fix the problem. For
77example, we could replace the declaration of `r` with the following.
78
79[source,sml]
80----
81val f: unit -> 'a option ref = fn () => ref NONE
82val r: 'a option ref = f ()
83----
84
85The declaration of `f` is well typed, while the declaration of `r`
86violates the value restriction because `f ()` is not a value.
87
88
89== Unnecessarily rejected programs ==
90
91Unfortunately, the value restriction rejects some programs that could
92be accepted.
93
94[source,sml]
95----
96val id: 'a -> 'a = fn x => x
97val f: 'a -> 'a = id id
98----
99
100The type constraint on `f` requires `f` to be polymorphic, which is
101disallowed because `id id` is not a value. MLton reports the
102following type error.
103
104----
105Error: z.sml 2.5-2.5.
106 Type of variable cannot be generalized in expansive declaration: f.
107 type: ['a] -> ['a]
108 in: val 'a f: ('a -> 'a) = id id
109----
110
111MLton indicates the inability to make `f` polymorphic by saying that
112the type of `f` cannot be generalized (made polymorphic) its
113declaration is expansive (not a value). MLton doesn't explicitly
114mention the value restriction, but that is the reason. If we leave
115the type constraint off of `f`
116
117[source,sml]
118----
119val id: 'a -> 'a = fn x => x
120val f = id id
121----
122
123then the program succeeds; however, MLton gives us the following
124warning.
125
126----
127Warning: z.sml 2.5-2.5.
128 Type of variable was not inferred and could not be generalized: f.
129 type: ??? -> ???
130 in: val f = id id
131----
132
133This warning indicates that MLton couldn't polymorphically generalize
134`f`, nor was there enough context using `f` to determine its type.
135This in itself is not a type error, but it it is a hint that something
136is wrong with our program. Using `f` provides enough context to
137eliminate the warning.
138
139[source,sml]
140----
141val id: 'a -> 'a = fn x => x
142val f = id id
143val _ = f 13
144----
145
146But attempting to use `f` as a polymorphic function will fail.
147
148[source,sml]
149----
150val id: 'a -> 'a = fn x => x
151val f = id id
152val _ = f 13
153val _ = f "foo"
154----
155
156----
157Error: z.sml 4.9-4.15.
158 Function applied to incorrect argument.
159 expects: [int]
160 but got: [string]
161 in: f "foo"
162----
163
164
165== Alternatives to the value restriction ==
166
167There would be nothing wrong with treating `f` as polymorphic in
168
169[source,sml]
170----
171val id: 'a -> 'a = fn x => x
172val f = id id
173----
174
175One might think that the value restriction could be relaxed, and that
176only types involving `ref` should be disallowed. Unfortunately, the
177following example shows that even the type `'a -> 'a` can cause
178problems. If this program were allowed, then we could cast an integer
179to a string (or any other type).
180
181[source,sml]
182----
183val f: 'a -> 'a =
184 let
185 val r: 'a option ref = ref NONE
186 in
187 fn x =>
188 let
189 val y = !r
190 val () = r := SOME x
191 in
192 case y of
193 NONE => x
194 | SOME y => y
195 end
196 end
197val _ = f 13
198val _ = f "foo"
199----
200
201The previous version of Standard ML took a different approach
202(<!Cite(MilnerEtAl90)>, <!Cite(Tofte90)>, <:ImperativeTypeVariable:>)
203than the value restriction. It encoded information in the type system
204about when ref cells would be created, and used this to prevent a ref
205cell from holding multiple types. Although it allowed more programs
206to be type checked, this approach had significant drawbacks. First,
207it was significantly more complex, both for implementers and for
208programmers. Second, it had an unfortunate interaction with the
209modularity, because information about ref usage was exposed in module
210signatures. This either prevented the use of references for
211implementing a signature, or required information that one would like
212to keep hidden to propagate across modules.
213
214In the early nineties, Andrew Wright studied about 250,000 lines of
215existing SML code and discovered that it did not make significant use
216of the extended typing ability, and proposed the value restriction as
217a simpler alternative (<!Cite(Wright95)>). This was adopted in the
218revised <:DefinitionOfStandardML:Definition>.
219
220
221== Working with the value restriction ==
222
223One technique that works with the value restriction is
224<:EtaExpansion:>. We can use eta expansion to make our `id id`
225example type check follows.
226
227[source,sml]
228----
229val id: 'a -> 'a = fn x => x
230val f: 'a -> 'a = fn z => (id id) z
231----
232
233This solution means that the computation (in this case `id id`) will
234be performed each time `f` is applied, instead of just once when `f`
235is declared. In this case, that is not a problem, but it could be if
236the declaration of `f` performs substantial computation or creates a
237shared data structure.
238
239Another technique that sometimes works is to move a monomorphic
240computation prior to a (would-be) polymorphic declaration so that the
241expression is a value. Consider the following program, which fails
242due to the value restriction.
243
244[source,sml]
245----
246datatype 'a t = A of string | B of 'a
247val x: 'a t = A (if true then "yes" else "no")
248----
249
250It is easy to rewrite this program as
251
252[source,sml]
253----
254datatype 'a t = A of string | B of 'a
255local
256 val s = if true then "yes" else "no"
257in
258 val x: 'a t = A s
259end
260----
261
262The following example (taken from <!Cite(Wright95)>) creates a ref
263cell to count the number of times a function is called.
264
265[source,sml]
266----
267val count: ('a -> 'a) -> ('a -> 'a) * (unit -> int) =
268 fn f =>
269 let
270 val r = ref 0
271 in
272 (fn x => (r := 1 + !r; f x), fn () => !r)
273 end
274val id: 'a -> 'a = fn x => x
275val (countId: 'a -> 'a, numCalls) = count id
276----
277
278The example does not type check, due to the value restriction.
279However, it is easy to rewrite the program, staging the ref cell
280creation before the polymorphic code.
281
282[source,sml]
283----
284datatype t = T of int ref
285val count1: unit -> t = fn () => T (ref 0)
286val count2: t * ('a -> 'a) -> (unit -> int) * ('a -> 'a) =
287 fn (T r, f) => (fn () => !r, fn x => (r := 1 + !r; f x))
288val id: 'a -> 'a = fn x => x
289val t = count1 ()
290val countId: 'a -> 'a = fn z => #2 (count2 (t, id)) z
291val numCalls = #1 (count2 (t, id))
292----
293
294Of course, one can hide the constructor `T` inside a `local` or behind
295a signature.
296
297
298== Also see ==
299
300* <:ImperativeTypeVariable:>