I found this video very helpful, since I was indeed confused about the two
meanings of impredicativity that I had seen floating around. One used by haskellers,
which was that you can't instantiate a type variable a with a ( forall t).
Impredicative in Coq means having (Type : Type).
- polymorphic types:
forall p. [p] -> [p] -- LEGALInt -> (forall p. [p] -> [p]) -- ILLEGAL(forall p. [p] -> [p]) -> Int -- ILLEGAL[forall a. a -> a] -- ILLEGAL- Higher rank types:
forallat the outermost level of a let-bound function, and to the left and right of arrows!
Int -> (forall p. [p] -> [p]) -- LEGAL(forall p. [p] -> [p]) -> Int -- LEGALrunST :: (forall s. ST s a) -> a -- LEGAL[forall a. a -> a] -- ILLEGAL- Impredicative type:
[forall a. a -> a]- We can't type
runSTbecause of impredicativity:
($) :: forall a, forall b, (a -> b) -> a -> brunST :: forall a, (forall s, ST s a) -> a -- LEGALst :: forall s. ST s IntrunST st -- YESrunST $ st -- NO- Expanding out the example:
($) runST st($) @ (forall s. ST s Int) @Int (runST @ Int) st- Data structures of higher kinded things. For example. we might want to have
[∀ a, a -> a] - We have
ids :: [∀ a, a -> a]. I also have the functionid :: ∀ a, a -> a. I want to buildids' = (:) id ids. That is, I want to cons anidonto my listids. - How do we type infer this?
§ How does ordinary type inference work?
reverse :: ∀ a. [a] -> [a]and :: [Bool] -> Boolfoo = \xs -> (reverse xs, and xs)- Start with
xs :: αwhereαis a type variable. - Typecheck
reverse xs. We need to instantiatereverse. With what type? that's what we need to figure out! - (1) Instantiate: Use variable
β. So we have that our occurence ofreversehas typereverse :: [β] -> [β]. - (2) Constrain: We know that
xs :: αandreverseexpects an input argument of type[β], so we setα ~ [β]due to the callreverse xs. - We now need to do
and xs. (1)anddoesn't have any type variables, so we don't need to perform instantiation. (2) We can constrain the type, becauseand :: [Bool] -> Bool, we can infer fromand xsthatα ~ [Bool] - We solve using Robinson unification . We get
[β] ~ α ~ [Bool]orβ = Bool
§ Where does this fail for polytypes?
- The above works because
αandΒonly stand for monotypes . - Our constraints are equality constraints , which can be solved by Robinson unification
- And we have only one solution (principal solution)
- When trying to instantiate reverse, how do we instantiate it?
- Constraints become subsumption constraints
- Solving is harder
- No principal solution
- Consider
incs :: [Int -> Int], and(:) id incsversus(:) id ids.
§ But it looks so easy!
- We want to infer
(:) id ids - We know that the second argument
idshad type[∀ a, a -> a] - we need a type
[p]for the second argument, because(:) :: p -> [p] -> [p] - Thus we must have
p ~ (∀ a, a -> a) - We got this information from the second argument
- So let's try to treat an application
(f e1 e2 ... en)as a whole.
§ New plan
- Assume we want to figure out
filter g ids. - start with
filter :: ∀ p, (p -> Bool) -> [p] -> Bool - Instatiate
filterwith instantiation variablesκto get(κ -> Bool) -> [κ] -> Bool - Take a "quick look" at
e1, e2to see if we know thatκshould be - We get from
filter g idsthatκ := (∀ a, a -> a). - Substitute for
κ(1) the type that "quick look" learnt, if any, and (2) A monomorphic unification variable otherwise. - Typecheck against the type. In this case, we learnt that
κ := (∀ a, a -> a), so we replaceκwith(∀ a, a -> a). - Note that this happens at each call site !
§ The big picture
Replace the idea of:
- instantate function with unficiation variables, with the idea.
- instantiate function with a quick look at the calling context.
- We don't need fully saturated calls. We take a look at whatever we can see!
- Everything else is completely unchanged.