As noted by glennsl in comments, per the manual the type variables (e.g. 'a
) are defined for the entire toplevel definition where they occur.
In general the scope of a named type variable is the whole top-level phrase where it appears
Consider defining a simple function f
with a locally scoped function g
which specifies a type of 'a -> 'a
:
# let f x y =
let g (x : 'a) : 'a = x in
(g x, g y);;
val f : 'a -> 'a -> 'a * 'a = <fun>
Because 'a
is scoped to the definition of f
it has to be unified accordingly. The only way this can work is if x
and y
have the same type.
There is no such restriction if we elide the explicit type annotation on g
.
# let f x y =
let g x = x in
(g x, g y);;
val f : 'a -> 'b -> 'a * 'b = <fun>
Another example. Starting without explicit type annotations. The compiler correctly infers that all four arguments must have wildcard types. It uses 'a
through 'd'
.
# let f a b c d =
let g x y = (x, y) in
let h x y = (x, y) in
(g a b, h c d);;
val f : 'a -> 'b -> 'c -> 'd -> ('a * 'b) * ('c * 'd) = <fun>
And adding the explicit type annotation, the compiler infers that all four arguments must have wildcard types, but because we have already via passing them as arguments to g
and h
given those types names, a
and c
mst both save the same type, as must b
and d
.
# let f a b c d =
let g (x : 'a) (y : 'b) : ('a * 'b) = (x, y) in
let h (x : 'a) (y : 'b) : ('a * 'b) = (x, y) in
(g a b, h c d);;
val f : 'a -> 'b -> 'a -> 'b -> ('a * 'b) * ('a * 'b) = <fun>