Imagine the following situation:
foo :: Show a => (a -> String) -> String -> Int -> IO () foo show' string int = do putStrLn (show' string) putStrLn (show' int)
Here, we want to pass in a function that converts a value into a String, apply that function to both a string parameter and and int parameter and print them both. In my mind, there is no reason this should fail! We have a function that works on both types of the parameters we're passing in.
Unfortunately, this won't type check! GHC infers the
a type based off of its first occurrence in the function body. That is, as soon as we hit:
putStrLn (show' string)
GHC will infer that
show' :: String -> String, since
string is a
String. It will proceed to blow up while trying to
RankNTypes lets you instead write the type signature as follows, quantifying over all functions that satisfy the
foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()
This is rank 2 polymorphism: We are asserting that the
show' function must work for all
as within our function, and the previous implementation now works.
RankNTypes extension allows arbitrary nesting of
forall ... blocks in type signatures. In other words, it allows rank N polymorphism.