Chapter 5. Patterns of Type Abstraction

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's type system has developed extremely anarchically. Many of the new features were sketched, implemented, and applied well before they were formalized. This anarchy has both strengths and weaknesses. The strength is that the design space is explored much more quickly the weakness is that the end result is extremely complex, and programs are sometimes reduced to experiments to see what will and will not be acceptable to the compiler."

 
 --Hudak et al., History of Haskell

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.

  • Abstracting function types: RankNTypes
  • Abstracting datatypes: Existential quantification, phantom types, generalized algebraic datatypes (GADTs), type case pattern, dynamic types, and heterogeneous lists
  • Abstracting type-classes: Multiparameter type-classes and functional dependencies

Abstracting function types: RankNTypes

Consider the following higher­order 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset