next up previous contents
Next: Conditional polymorphism Up: Functional programming in Haskell Previous: Functions as first class   Contents

Polymorphism

The apply function defined earlier specifically requires the first argument to be a function with type Float -> Int and, hence, the second argument to be of type Float.

It would be more desirable to be able to write a version of apply whose first argument has an arbitrary function type T -> T' and whose second argument is of a matching type T, so that apply itself has the type (T -> T') -> T -> T'.

This is an example of polymorphism. The word means ``taking multiple forms'' and refers to a function that operates similarly on multiple types. An even more basic example of a polymorphic function is the identity function that just returns its argument. This function should have type T -> T, for any type T.

Other examples of polymorphism are familiar functions on lists. For instance, reverse reorders a list of any type, so reverse has type [T] -> [T] for all types T (recall that for a type T, the list of that type has type [T]). Similarly, length returns the length of any list, so length should have type [T] -> Int.

This kind of polymorphism is sometimes referred to as ``structural'' polymorphism because of the way it is reflected in functions such as reverse and length, where the function operates on the structure of the collection rather than on the base type.

One must be careful to distinguish this kind of polymorphism from the ad hoc variety associated with overloading operators. For instance, in most programming languages, we write + to denote addition for both integers and floating point numbers. However, since the underlying representations used for the two kinds of numbers are completely different, we are actually using the same name (+, in this case) to designate functions that are computed in a different manner for different base types.

Another example of ad hoc polymorphism is that available in Java, where different methods can have the same name but different parameter types. Thus, for instance, the class Arrays seemingly supports a uniform method called sort to sort arrays of arbitrary scalar types, but this is just a collection of distinct functions with the same name--the correct one is picked depending on the type of argument passed to the function.

Coming back to Haskell, we can describe polymorphic types using type variables, typically denoted a, b, c, .... Thus we have the following type definitions for the functions discussed earlier.

  identity :: a -> a
  apply :: (a -> b) -> a -> b
  length :: [a] -> Int
  reverse :: [a] -> [a]

Type variables come with implicit universal quantification. Thus, for instance, we read the type of apply as ``for every type a and every type b, (a -> b) -> a -> b''. Type variables are uniformly substituted by ``real'' types to get a specific instance of the function. Thus, for instance, when we define a function

  plus2 :: Int -> Int
  plus2 n = 2+n

and then write

  apply plus2 5

this first instantiates both the variables a and b to type Int and uses the concrete definition

  apply :: (Int -> Int) -> Int -> Int

to do the job.


next up previous contents
Next: Conditional polymorphism Up: Functional programming in Haskell Previous: Functions as first class   Contents
Madhavan Mukund 2004-04-29