Haskell/Polymorphism

Parametric Polymorphism
Section goal = short, enables reader to read code (ParseP) with ∀ and use libraries (ST) without horror. Question Talk:Haskell/The_Curry-Howard_isomorphism would be solved by this section.

Link to the following paper: Luca Cardelli: On Understanding Types, Data Abstraction, and Polymorphism.

A polymorphic function is a function that works for many different types. For instance,

can calculate the length of any list, be it a string  or a list of integers. The type variable  indicates that   accepts any element type. Other examples of polymorphic functions are:

Type variables always begin in lowercase whereas concrete types like  or   always start with an uppercase letter. This allows us to tell them apart.

There is a more explicit way to indicate that  can be any type

In other words, "for all types, the function   takes a list of elements of type   and returns an integer". You should think of the old signature as an abbreviation for the new one with the  keyword. That is, the compiler will internally insert any missing  for you. Another example: the types signature for  is really a shorthand for:

or equivalently:

Similarly, the type of  is really:

This notion, that something is applicable to every type or holds for everything, is called universal quantification. In mathematical logic, the symbol ∀ (an upside-down A, read as "for all") is commonly used for that; it is called the universal quantifier.

Higher rank types
With explicit, it now becomes possible to write functions that expect polymorphic arguments, like for instance

Here,  is a polymorphic function, it can be applied to anything. In particular,  can apply it to both the character   and the boolean.

It is not possible to write a function like  in Haskell98, the type checker will complain that   may only be applied to values of either the type   or the type   and reject the definition. The closest we could come to the type signature of  would be

which is the same as

But this is very different from. The  at the outermost level means that   promises to work with any argument   as long as   has the shape   for some type   unknown to. Contrast this with, where it's the argument   which promises to be of shape   for all types   at the same time , and it's   which makes use of that promise by choosing both   and.

Concerning nomenclature, simple polymorphic functions like  are said to have a rank-1 type while the type   is classified as rank-2 type. In general, a rank-n type is a function that has at least one rank-(n-1) argument but no arguments of any higher rank.

The theoretical basis for higher rank types is System F, also known as the second-order lambda calculus. We will detail it in the section System F in order to better understand the meaning of  and its placement like in   and.

Haskell98 is based on the Hindley-Milner type system, which is a restricted version of System F and does not support  and rank-2 types or types of even higher rank. You have to enable the language extension to make use of the full power of System F.

There is a good reason that Haskell98 does not support higher rank types: type inference for the full System F is undecidable; the programmer would have to write down all type signatures. So, the early versions of Haskell have adopted the Hindley-Milner type system which only offers simple polymorphic functions but enables complete type inference in exchange. Recent advances in research have reduced the burden of writing type signatures and made rank-n types practical in current Haskell compilers.

For the practical Haskell programmer, the ST monad is probably the first example of a rank-2 type in the wild. Similar to the IO monad, it offers mutable references

and mutable arrays. The type variable  represents the state that is being manipulated. But unlike IO, these stateful computations can be used in pure code. In particular, the function

sets up the initial state, runs the computation, discards the state and returns the result. As you can see, it has a rank-2 type. Why?

The point is that mutable references should be local to one. For instance,

is wrong because a mutable reference created in the context of one  is used again in a second. In other words, the result type  in   may not be a reference like   in the case of. But the rank-2 type guarantees exactly that! Because the argument must be polymorphic in, it has to return one and the same type   for all states  ; the result   may not depend on the state. Thus, the unwanted code snippet above contains a type error and the compiler will reject it.

You can find a more detailed explanation of the ST monad in the original paper Lazy functional state threads.

Impredicativity

 * predicative = type variables instantiated to monotypes. impredicative = also polytypes. Example:  or  . Subtly different from higher-rank.


 * relation of polymorphic types by their generality, i.e. `isInstanceOf`.
 * haskell-cafe: RankNTypes short explanation.

Type classes
TODO

System F
Section goal = a little bit lambda calculus foundation to prevent brain damage from implicit type parameter passing.


 * System F = Basis for all this ∀-stuff.
 * Explicit type applications i.e. . ∀ similar to the function arrow ->.
 * Terms depend on types. Big Λ for type arguments, small λ for value arguments.

Examples
Section goal = enable reader to judge whether to use data structures with ∀ in his own code.


 * Church numerals, Encoding of arbitrary recursive types (positivity conditions):
 * Continuations, Pattern-matching:,   and

I.e. ∀ can be put to good use for implementing data types in Haskell.

Other forms of Polymorphism
So far we talked about primarily parametric polymorphism. There are however two more forms of polymorphism that are predominantly employed in other programming languages:
 * Ad-hoc polymorphism
 * Subtype Polymorphism

Ad-hoc polymorphism
In C++, ad-hoc polymorphism can be seen as equivalent to function overloading: We can do something similar in Haskell using type classes: The main thing to take away with ad-hoc polymorphism is there will always be types that the function cannot accept, though the number of types the function can accept may be infinite.

Contrast this with parametric polymorphism, equivalent in C++ to template functions: Which is equivalent to the following in Haskell: The main take-away with parametric polymorphism is that any type must be accepted as an input to the function, regardless of its return type.

Note, with both forms of polymorphism, it is not possible to have two identically named functions that differ only in their return type.

For example, the following C++ is not valid: Nor the Haskell version: Since the compiler would have no way to determine which version to use given an arbitrary function call.

Subtype polymorphism
TODO = contrast polymorphism in OOP and stuff.

Free Theorems
Section goal = enable reader to come up with free theorems. no need to prove them, intuition is enough.
 * free theorems for parametric polymorphism.