Let me use a simpler example to illustrate the problem.
cons0 is a definition of a polymorphic function defined in the global domain. This is just a trivial wrapper for the :: operator. Not surprisingly, the definition works. cons1 almost matches cons0 , except that its scope is limited to the expression in body in . Changing the area looks harmless, and, of course, these are typechecks. cons3 again the same function without type annotation, and we can use it polymorphically in the in body.
And what happened to cons2 ? The problem is 'a : area: this is a whole top-level phrase. The semantics of the phrase that cons2 defines are
for some type 'a, (let cons2 (x : 'a) (y : 'a list) = ... in ...)
Since 'a must be compatible with int (due to cons3 1 [] ) and with bool (due to cons3 true [] , it is not possible to instantiate 'a . Therefore, the phrase is not typical.
If you like to think about what ML is typed in terms of a regular type determination algorithm, each explicit user variable introduces a set of constraints in the unification algorithm. Here the limitations are 'a = "type of the parameter x" and ' = "type of the parameter y" . But region 'a is the whole phrase, it does not generalize in any inner region. So int and bool both combine into non-generic 'a .
Recent versions of OCaml introduce type variables (as in Nicki Yoshiuchi's Answer ). The same effect could be achieved using the local module in earlier versions (≥2.0):
let module M = struct let cons2 (x : 'a) (y : 'a list) = x :: y end in (M.cons2 1 [], M.cons2 true []);;
(Standard MLers note: this is one place where OCaml and SML differ.)
Gilles
source share