Higher-rank Polymorphism
On the functional programming course I attended, I learnt about some interesting parts of Haskell’s type system. I figured I’d write a blog post*.
(Standard) Haskell has what’s called let-bound polymorphism. This means that identifiers bound in a let
, a where
or top-level definitions can be polymorphic, whereas lambda-bound identifiers (function arguments) cannot. Here are some examples, using the (polymorphic) id function:
This last example does not work: it is not possible to determine the type of f
, and no explicit type signature has been provided**. How do we fix this? We need to give the compiler more type information!
How do we describe the type of f
? Well, it’s a polymorphic function of type a -> a
. So the type of the inline lambda abstraction above is something like (∀a.a -> a) -> (Integer, Char)
. This is an example of higher-rank polymorphism – this is where a polymorphic type appears in another type; in this case the function argument itself is polymorphic (a so-called rank-2 type).
How do we express that in Haskell? Well, we need two GHC extensions; namely ScopedTypeVariables and RankNTypes. RankNTypes
allows us to express ‘for all’ (and so describe higher-rank types), and ScopeTypeVariables allows us to extend the scope of type variables into the definition of a function.
et voila!
See here for the full example source code, and here for more Haskell.
*Oh, and if you’re a Haskeller and have noticed some mistake in this post, let me know, feedback welcome!
**I think GHC assumes f
to be monomorphic because of the monomorphism restriction