Value restriction
In Hindley–Milner type inference, let x = e1 in e2 generalizes the type of e1 over its free type variables, so that x can be used at different types in e2[6]. With mutable state this is unsound: if ref [] had type 'a list ref for every 'a, one use could store an int in it and another read a string out of it[3]. The value restriction, proposed by Wright in 1995[1] and adopted by Standard ML in 1997[5], generalizes only syntactic values, since evaluating a value cannot allocate state. It replaced more complex systems of imperative type variables[3][4]. Type variables that cannot be generalized are weak: they stand for one unknown type, fixed by the first use. OCaml generalizes additionally any variable that occurs only in covariant positions, following Garrigue[2].
Example
A value is a constant, a variable, a function, or a constructor applied to values. Anything else is an application.
let r = ref []let id_list = List.map (fun x -> x)let id_list' l = List.map (fun x -> x) llet empty = List.rev []
What ocamlc -i infers.
val r : '_weak1 list refval id_list : '_weak2 list -> '_weak2 listval id_list' : 'a list -> 'a listval empty : 'a list
The fix for id_list is eta-expansion: adding the parameter back turns the application into a function, which is a value. The last line is the relaxation: 'a appears in the type of empty only as the element type of a list, a covariant position, so it can be generalized even though List.rev [] is an application.
Why references cannot be polymorphic
The type rule for let in Damas–Milner inference generalizes over the variables not free in the environment[6]:
This is sound for a pure language, where denotes the same value however often is used. When allocates a reference, every use of refers to the same cell, and a polymorphic type for it lets different uses disagree about what it contains. The value restriction adds a side condition: generalize only if is a syntactic value; otherwise bind with its variables left free[1].
A weak variable is not an error by itself. It is fixed by the first use that constrains it:
A weak reference and its first use.
let r = ref []let () = r := [ 1; 2 ]let first = List.hd !r
What ocamlc -i infers.
val r : int list refval first : int
A second use at another type.
let r = ref []let () = r := [ 1; 2 ]let () = print_endline (List.hd !r)
The compiler's error.
File "clash.ml", line 3, characters 23-35:3 | let () = print_endline (List.hd !r)^^^^^^^^^^^^Error: This expression has type int but an expression was expected of typestring
It is an error only if a compilation unit ends with a weak variable still unfixed in its interface, since another unit could then fix it differently[8].
A module whose only definition is a weak reference.
let r = ref []
The compiler's error.
File "unit.ml", line 1, characters 4-5:1 | let r = ref []^Error: The type of this expression, '_weak1 list ref,contains type variables that cannot be generalized
The unsoundness, concretely
Haskell has no value restriction, because references are created by IO actions and a let cannot run one. unsafePerformIO breaks that separation, and its documentation warns that it can then be used to write a polymorphic reference and so coerce between types[7].
A polymorphic reference in Haskell.
import Data.IORefimport System.IO.Unsafe (unsafePerformIO)-- A reference created outside IO gets a polymorphic type, exactly what-- the value restriction forbids in ML.cell :: IORef [a]cell = unsafePerformIO (newIORef []){-# NOINLINE cell #-}main :: IO ()main = dowriteIORef cell [1 :: Int]xs <- readIORef cellprint (head xs :: Double) -- the Int's bits read as a Double
Running it: the bits of the Int 1, read as a Double.
5.0e-324
This is the program the value restriction rules out in ML.
The relaxed value restriction
The restriction rejects many harmless programs, especially partial applications of polymorphic functions. Garrigue observed that a type variable occurring only in covariant positions, such as the element type of an immutable list or the result of a function, cannot be used to store a value of one type and retrieve it at another, since nothing of that type can be passed in[2]. OCaml therefore generalizes such variables even in applications. For abstract types, the variance must be declared in the signature, with + for covariant or - for contravariant.
The same function behind two signatures, one declaring covariance.
(* Two abstract container types, one declared covariant. *)module Inv : sigtype 'a tval make : unit -> 'a tend = structtype 'a t = 'a listlet make () = []endmodule Cov : sigtype +'a tval make : unit -> 'a tend = structtype 'a t = 'a listlet make () = []endlet a = Inv.make ()let b = Cov.make ()
What ocamlc -i infers.
module Inv : sig type 'a t val make : unit -> 'a t endmodule Cov : sig type +'a t val make : unit -> 'a t endval a : '_weak1 Inv.tval b : 'a Cov.t
Without the annotation, 'a Inv.t might be a mutable structure, and the type checker must assume the worst.
Earlier approaches
Before the value restriction, ML implementations tracked which type variables could reach a reference. Tofte's imperative type variables of 1987–1990 marked variables that might be stored in a reference and refused to generalize them when an expression could allocate[3], and the Definition of Standard ML of 1990 adopted them; SML of New Jersey used a refinement, weak type variables of numbered strength. Leroy and Weis's closure typing tracked the types of values captured in closures[4]. These systems accepted more programs, but their types were complicated and exposed implementation details in module signatures. Wright showed that almost all real programs type-check under the simple syntactic restriction, most of the rest after eta-expansion[1], and Standard ML adopted it in its 1997 revision[5]. OCaml adopted the relaxed form in version 3.07 in 2003[2].
see also
- Polymorphic variantA polymorphic variant is a variant whose constructors, called tags and written with a backquote in OCaml, exist independently of any type declaration. Their types are sets of tags with bounds: [> `A] means at least `A, [< `A | `B] means at most those two, and unification works out unions and intersections of those sets using row variables. They let functions share constructors across types, be extended with new cases without editing old code, and encode subtyping, at the cost of longer types and error messages and of typos in open types going unnoticed.
- Phantom typeA phantom type is a type parameter that appears in a type's declaration but in none of its fields, used to keep otherwise identical representations apart: lengths in metres and in feet, validated and unvalidated strings, read-only and writable handles, SQL expressions of different result types. It carries no data and costs nothing at run time. Combined with an abstract type whose constructors are hidden, it lets a library enforce invariants through the type checker. Unlike a GADT index it tells a pattern match nothing; it constrains what callers may do, not what the compiler can learn.
- LensA lens is a first-class pair of a getter and a setter for one part of a structure, with laws that make them agree: you get back what you set, setting what you got changes nothing, and a second set overwrites the first. Lenses compose, so a path through nested immutable records is one value that can read or update the part at its end. They originated in work on the view-update problem, and in Haskell are usually encoded as functions polymorphic in a functor, which makes composition ordinary function composition.
- GADTA generalized algebraic data type (GADT) is an algebraic data type whose constructors may each give a more specific result type than the declared one, instead of all sharing one polymorphic return type. Matching on a constructor then tells the type checker what the type parameter was, so a single well-typed eval can return an int for an Add node and a bool for an Eq node, checked at compile time rather than by a run-time tag test. GADTs express typed syntax trees, type representations and equality proofs, typed format strings and length-indexed data. The price is that type inference needs annotations.
- Type-directed disambiguationType-directed disambiguation is OCaml's rule, since version 4.01, for deciding which record type a field label, or which variant type a constructor, belongs to when several in scope share the name: if the expected type is already known at that point of type inference, it decides; otherwise the most recently defined type with that name wins. It also lets labels and constructors be used without being in scope when the type is known. Because the outcome depends on what inference knows at each point, the -principal flag warns when a choice relied on information that a different inference order might not have had.
referenced by
further reading
- [1]A. K. Wright, “Simple imperative polymorphism”, LISP and Symbolic Computation 8 (1995).
- [2]J. Garrigue, “Relaxing the value restriction”, FLOPS (2004).
- [3]M. Tofte, “Type inference for polymorphic references”, Information and Computation 89 (1990).
- [4]X. Leroy, P. Weis, “Polymorphic type inference and assignment”, POPL (1991).
- [5]R. Milner, M. Tofte, R. Harper, D. MacQueen, The Definition of Standard ML (Revised), MIT Press (1997).
- [6]L. Damas, R. Milner, “Principal type-schemes for functional programs”, POPL (1982).
- [7]The GHC base library documentation, System.IO.Unsafe.unsafePerformIO.
- [8]OCaml manual, “Polymorphism and its limitations”.
last updated