In the previous chapter, we explored several ways in which the Haskell 98 language has been evolving via new libraries. This chapter brings to focus some key advances on another major front of Haskell's evolution: language extensions.
Haskell extensions are tied to the compiler implementation rather than the language standard (we use the GHC 7.1 0 compiler as our reference). We'll explore the extensions along three axes: abstracting functions, datatypes, and type-classes.
Consider the following higherorder function which maps the argument function to each tuple element:
tupleF elemF (x, y) = (elemF x, elemF y)
Left to its own devices, the Haskell 98 compiler will infer this type for the tupleF
function:
tupleF :: (a -> b) -> (a, a) -> (b, b)
As elemF
is applied to x
and y
, the compiler assumes that x
and y
must be of the same type, hence the inferred tuple type (a,a)
. This allows us to do the following:
tupleF length ([1,2,3], [3,2,1]) tupleF show (1, 2) tupleF show (True, False)
However, we cannot do this:
tupleF show (True, 2) tupleF length ([True, False, False], [1, 2, 4])
RankNTypes allow us to enforce parametric polymorphism explicitly. We want tupleF
to accept a polymorphic function; in other words we want our function to have a "higher rank type", in this case Rank 2
:
{-# LANGUAGE Rank2Types #-} tupleF' :: (Show a1, Show a2) => (forall a . Show a => a -> b) -- elemF -> (a1, a2) -> (b, b) tupleF' elemF (x, y) = (elemF x, elemF y)
The use of forall
in the elemF
function signature tells the compiler to make elemF
polymorphic in a
, as shown:
main = do
-- same as before...
print $ tupleF' show (1, 2)
print $ tupleF' show (True, False)
-- and now we can do this...
print $ tupleF' show (True, 2)
We can see the polymorphism clearly in the last line of the preceding code, where show
is applied polymorphically to the types Bool
and Int
.
The rank of a type refers to the nesting depth at which polymorphism occurs. Rank 0
describes absence of polymorphism. For example:
intAdd :: Int -> Int -> Int
Rank 1
refers to regular parametric polymorphism, for example:
id :: a -> a
while Rank 2
to Rank n
describes more deeply nested forms of polymorphism. However, the deeper the level of polymorphism, the rarer and more exotic the application. Having said that,
"These cases are not all that common, but there are usually no workarounds; if you need higher-rank types, you really need them!" | ||
--Peyton Jones et al., 2007, Practical type inference for arbitrary-rank types |