![]() |
Edit |
![]() |
|
![]() |
Recent Changes |
![]() |
Subscriptions |
![]() |
Lost and Found |
![]() |
Find References |
![]() |
Rename |
| Search |
![]() |
List all versions |
The "Value Restriction" in languages such as F#, OCaml and StandardML ensures thst the language need only execute code when it has concrete (ground) types for all relevant type variables. It is covered in depth elsewhere, e.g. section 5.1.1 of http://www.cs.caltech.edu/courses/cs134/cs134b/book.pdf.
As a result of this, some innocuous bindings are not generalized (i.e. given generic types) in the way that you might hope. Ungeneralized type variables remain as unresolved unknowns in the type inference problem. By the end of a scope of type inference all type variables must have been either generalized or given ground values..
This can lead to differences in behaviour between FSI and FSC (see FSIDifferencesInBehaviour).
In F# Interactive and the F# command line compiler the value restriction will give errors such as the one shown below:
stdin(2,4): error: FS0030: Type inference has inferred the signature
val trtsucc : 'a expr
for 'trtsucc', but the definition of 'trtsucc' is not a simple value, and cannot
be run before specific types have been given for the type parameters in the
inferred type. Either define 'trtsucc' as a simple data term, make it a function,
or add a type constraint to instantiate the type parameters.
The value restriction sometimes means you have to change "functions defined as values" into "proper functions". For example:
/// Take the first n elements of the list.
let rec take n l =
match n, l with
| 0, _ -> []
| _, [] -> []
| _, (hd :: tl) -> hd :: take (n - 1) tl;;
/// Drop the first n elements of the list and return the rest.
let drop n l =
let rec drop' n l =
match n, l with
| 0, _ -> l
| _, [] -> []
| _, (_ :: tl) -> drop' (n - 1) tl in
drop' n l
/// Chops a list into chunks of size n.
let rec chop n l =
match l with
| [] -> []
| xs -> take n xs :: chop n (drop n xs);;
/// The size of a box side
let boxsize = 3;;
/// Splits a list into boxsize chunks .
let split = chop boxsize;;
Here the value restriction bites with "split": the definition o"chop boxsize" looks innocuous but is not a "simple" function value (it's a function application that eventually computes a function value: in this case quite obviously so through partial application, but nevertheless the F# type system doesn't know this). This means you get an error such as
stdin(24,4): error: FS0030: Value restriction. Type inference has
inferred the signature
val split : ('_a list -> '_a list list)
but 'split' is not a syntactic function. Either define 'split' as a
syntactic function, or add a type constraint to instantiate the type
parameters.
The solution here is to the first choice, i.e. "define split as a syntactic function", so write:
let split l = chop boxsize l;;
The value restriction in F# sometimes restricts more thatn in OCaml. For example, consider a function for calculating the cross-product of two lists:
let product l1 l2 =
List.concat (List.map (fun x -> List.map (fun y -> (x, y)) l2) l1)
It works, but when I try to call product with one empty list as argument I get:
> product [1; 2] [];;
stdin(0,0): error: FS0030: Value restriction. Type inference has inferred the signature
val it : (int * '_a) list
but its definition is not a simple data constant. Either define 'it'
as a simple data term, make it a function, or add a type constraint to instantiate the type parameters.
stopped due to error
A minimal case that reproduced the same problem is:
> List.map (fun x -> x) [];;
stdin(0,0): error: FS0030: Value restriction. Type inference has inferred the signature
val it : '_a list
but its definition is not a simple data constant. Either define 'it'
as a simple data term, make it a function, or add a type constraint to instantiate the type parameters.
stopped due to error
The same examples do not hit the value restriction in OCaml (3.09.3) :
# let prod l1 l2 = List.concat (List.map (fun x -> List.map (fun y -> (x, y)) l2) l1);; val prod : 'a list -> 'b list -> ('a * 'b) list = <fun>
# prod [1; 2] [5; 6];;
- : (int * int) list = [(1, 5); (1, 6); (2, 5); (2, 6)]
# prod [1; 2] [];;
- : (int * 'a) list = []
# prod [] [];;
- : ('a * 'b) list = []
# List.map (fun x -> x) [];;
- : 'a list = []
The appropriate step is simply to add a type annotation to your scripting code, e.g.
(product [1; 2] [] : (int * int) list)
To explain the behaviour, first note that the "definition" of the value is the function application expression
"product [1; 2] []"
As far as F# is concerned this is not a simple value (the fact that it may eventually evaluate to a simple value is not important here - the type system doesn't know that)
OCaml has a special rule that handles this case: type variables that only in co-variant positions in a value can be generalized. However, this rule is not an appropriate rule for F# for a number of reasons, e.g. type constructors are not known to be co/contra variant in F#, and OCaml relies on the use of uniform representations for polymorphic values.
On the F# list Don Syme wrote: "I'm not that keen on extensions to type systems to reduce the incidence of the value restriction: in particular I find that it makes a difficult-to-explain subject even more difficult to explain, with only minor benefit to the advanced user. Do you want to be able to explain the value restriction to beginners, or to save advanced users a couple of type annotations? In F# we opted for the former."
![]() |
| This site supports the new NoFollow anti-spam initiative. |
Recent Topics