RankNTypes
Overview
Rank-1 polymorphism
Normal (rank-1) polymorphism is quantifying a type on the outside – with the RankNTypes language extension (and others), we can make that quantification visible, e.g.:
fmap :: forall f a b. Functor f => (a -> b) -> f a -> f b
The caller of the function is flexible and can choose at what instantiations of the quantified variables to call the function.
The callee (i.e., the function itself) is constrained and cannot make any assumptions about the quantified type variables in the implementation.
Rank-2 polymorphism
Rank-2 polymorphism is quantifying a function argument:
hoist :: Functor m1 => (forall c . m1 c -> m2 c) -> Stream b m1 a -> Stream b m2 a
The caller of the function is constrained and has to provide an argument that is sufficiently polymorphic.
The callee (i.e., the function itself) is flexible and can use the function argument at several different types in the implementation.
GHC cannot infer rank-2 (or higher) polymorphic types (similar to polymorphic recursion), and a type signature is required in such a case.
Code example
code:rank2.hs
foo f = f 1,2,3 + f "string" -- error {- In this case, (a -> Int) seems like it's a polymorphic function since it takes type variable a as an argument. But when you run it in standard Haskell, a will be infered as Int or Char therefore will not be polymorphic -}
foo :: (forall a . a -> Int) -> Int foo' f = f 1,2,3 + f "string" ghci> foo' length
9
{- This will compile since we're explicitly telling Haskell that function (a -> Int) is polymorphic -}
Useful links