Type checking can be made decidable by enriching the syntax appropriately. For example, in the paper, we have lambdas written as x -> e
; to type-check this, you must guess the type of x
. However, with a suitably enriched syntax, this can be written as x :: t -> e
instead, which takes the guess-work out of the process. Similarly, in the paper, they allow type-level lambdas to be implicit; that is, if e :: t
, then also e :: forall a. t
. To do typechecking, you have to guess when and how many forall
s to add, and when to eliminate them. As before, you can make this more deterministic by adding syntax: we add two new expression forms /a. e
and e [t]
and two new typing rule that says if e :: t
, then /a. e :: forall a. t
, and if e :: forall a. t
, then e [t ] :: t [t / a]
(where the brackets in t [t / a]
are substitution brackets). Then the syntax tells us when and how many foralls to add, and when to eliminate them as well.
So the question is: can we go from Haskell to sufficiently-annotated System F terms? And the answer is yes, thanks to a few critical restrictions placed by the Haskell type system. The most critical is that all types are rank one*. Without going into too much detail, "rank" is related to how many times you have to go to the left of an ->
constructor to find a forall
.
Int -> Bool -- rank 0?
forall a. (a -> a) -- rank 1
(forall a. a -> a) -> (forall a. a -> a) -- rank 2
In particular, this restricts polymorphism a bit. We can t type something like this with rank one types:
foo :: (forall a. a -> a) -> (String, Bool) -- a rank-2 type
foo polymorphicId = (polymorphicId "hey", polymorphicId True)
The next most critical restriction is that type variables can only be replaced by monomorphic types. (This includes other type variables, like a
, but not polymorphic types like forall a. a
.) This ensures in part that type substitution preserves rank-one-ness.
It turns out that if you make these two restrictions, then not only is type-inference decidable, but you also get minimal types.
If we turn from Haskell to GHC, then we can talk not only about what is typable, but how the inference algorithm looks. In particular, in GHC, there are extensions that relax the above two restrictions; how does GHC do inference in that setting? Well, the answer is that it simply doesn t even try. If you want to write terms using those features, then you must add the typing annotations we talked about all the way back in paragraph one: you must explicitly annotate where forall
s get introduced and eliminated. So, can we write a term that GHC s type-checker rejects? Yes, it s easy: simply use un-annotated rank-two (or higher) types or impredicativity. For example, the following doesn t type-check, even though it has an explicit type annotation and is typable with rank-two types:
{-# LANGUAGE Rank2Types #-}
foo :: (String, Bool)
foo = (f -> (f "hey", f True)) id
* Actually, restricting to rank two is enough to make it decidable, but the algorithm for rank one types can be more efficient. Rank three types already give the programmer enough rope to make the inference problem undecidable. I m not sure whether these facts were known at the time that the committee chose to restrict Haskell to rank-one types.