Last time we saw how we can make a substitution for a free variable to produce an instance of a type scheme. This time we’ll look at a much more powerful form of substitution.
By contrast a type-scheme σ = ∀α1...αm τ has a "generic instance" σ' = ∀β1...βn τ' if τ' = [τi/αi]τ for some types τ1,...,τm and the βj are not free in σ.
This definitely needs an example.
Suppose we have the identity function
λx.x. Its type scheme is
∀α α → α. Now we might want to perform a substitution here of
α. But we’ve only defined substitution on free variables, and we need to make a substitution on bound variables, to eliminate the bound variable entirely.
This mechanism of “generic instantiation” says: a type scheme consists of zero or more quantifiers and a type. Take just the type and perform a substitution on it, and then put as many quantifiers as you like before the substituted type, provided that the new quantifiers are not turning free variables into bound variables.
Let’s try it. Extract the type from the type scheme:
α → α. We perform our substitution
[int/α] on that type to get
int → int. And then we put zero or more quantifiers on top of that. Let’s put zero quantifiers on top of it. So we have the original type scheme
∀α α → α — that is, for all α, there’s a function from α to α. Now we want a specific generic instance of that, and it is
int → int.
This is in a sense the sort of generic instantiation that you’re used to from C#. Notice that we’ve eliminated not just the type variable, but the quantifier on top of the type scheme.
Now let’s look at a more complicated example.
Suppose we have some type scheme for some function, let’s say
∀β∀γ β → γ. And let’s suppose we pass that thing to the identity function. The type scheme of the identity function is
∀α α → α: the quantifier says that this works for any
α we care to name, as long as the type provided for
α is a type, not a type scheme; remember, so far we’ve only described type substitution by saying that we’re going to replace a free variable with a type, but what we have is a type scheme with two bound type variables:
∀β∀γ β → γ.
How do we deal with this? We’ll do our generic instantiation process again. We have type schemes
∀α α → α and
∀β∀γ β → γ. Take just the type of the first without the quantifiers:
α → α. Now make the substitution with the type of the second without the quantifiers:
[β → γ / α] to get the type
(β → γ) → (β → γ). Then slap a couple of quantifiers on top of that:
∀β∀γ (β → γ) → (β → γ).
We’ve produced an entirely new type scheme, but clearly this type scheme has the same “fundamental structure” as the type scheme for the identity function, even though this type scheme does not have the type variable of the original scheme, or even the same number of type variables; it’s got one more.
Why did we have to say that all of the introduced type variables must be “not free”? Well, let’s see what happens if we violate that constraint. Suppose we have type scheme
∀α β → α. Clearly β is free.
We extract the type:
β → α. We perform a substitution
[int/α] to get
β → int. And then we put a quantifier on top:
∀β β → int. But that turns β from a free variable into a bound variable, so this is not a generic instance of a type scheme with a free variable β. Thus when creating generic instances we are restricted to introduce quantifiers only on non-free variables.
In this case we shall write σ > σ'.
Conceptually this is pretty straightforward. Given two type schemes, possibly one is a generic instance of the other. If a schema σ’ is a generic instance of a scheme σ then we say that σ’ is smaller than σ. This should make some sense given our examples so far. If you think of schemes as patterns that can be matched, the pattern
∀α α → α is way more general than the pattern
int → int, so we say that the latter is “smaller” — less general — than the former.
It is slightly problematic that the authors chose the “greater than” symbol instead of, say, the “greater than or equal” symbol. Why? Well consider
∀α α → α and
∀β β → β. It should not be hard to see that both of these are generic instances of each other, and therefore both are “smaller” than the other, which is silly. Plainly they are equally general!
Note that instantiation acts on free variables, while generic instantiation acts on bound variables.
This line should now be clear.
It follows that σ > σ' implies S σ > S σ'.
Let’s look at a quick example just to make sure that’s clear. Suppose we have
σ = ∀α α → β and
σ' = int → β. Clearly σ > σ’. If we now make the substitution of string for β in both, then we have
∀α α → string which is still more general than
int → string; clearly the greater-than relationship is preserved by substitution of free variables.
And that’s it for section 3! Next time, we’ll look at the formal semantics of the Exp language.