The Gentle Introduction To Haskell
back next

10  Typing Pitfalls

This short section give an intuitive description of a few common problems that novices run into using Haskell's type system.

10.1  Let-Bound Polymorphism

Any language using the Hindley-Milner type system has what is called let-bound polymorphism, because identifiers not bound using a let or where clause (or at the top level of a module) are limited with respect to their polymorphism. In particular, a lambda-bound function (i.e., one passed as argument to another function) cannot be instantiated in two different ways. For example, this program is illegal:

let f g  =  (g [], g 'a')                       -- ill-typed expression
in f (\x->x)

because g, bound to a lambda abstraction whose principal type is a->a, is used within f in two different ways: once with type [a]->[a], and once with type Char->Char.

10.2  Numeric Overloading

It is easy to forget at times that numerals are overloaded, and not implicitly coerced to the various numeric types, as in many other languages. More general numeric expressions sometimes cannot be quite so generic. A common numeric typing error is something like the following:

average xs              =  sum xs / length xs           -- Wrong!

requires fractional arguments, but length's result is an Int. The type mismatch must be corrected with an explicit coercion:

average                 :: (Fractional a) => [a] -> a
average xs              =  sum xs / fromIntegral (length xs)

10.3  The Monomorphism Restriction

The Haskell type system contains a restriction related to type classes that is not found in ordinary Hindley-Milner type systems: the monomorphism restriction. The reason for this restriction is related to a subtle type ambiguity and is explained in full detail in the Report (§4.5.5). A simpler explanation follows:

The monomorphism restriction says that any identifier bound by a pattern binding (which includes bindings to a single identifier), and having no explicit type signature, must be monomorphic. An identifier is monomorphic if is either not overloaded, or is overloaded but is used in at most one specific overloading and is not exported.

Violations of this restriction result in a static type error. The simplest way to avoid the problem is to provide an explicit type signature. Note that any type signature will do (as long it is type correct).

A common violation of the restriction happens with functions defined in a higher-order manner, as in this definition of sum from the Standard Prelude:

sum                     =  foldl (+) 0

As is, this would cause a static type error. We can fix the problem by adding the type signature:

sum                     :: (Num a) => [a] -> a

Also note that this problem would not have arisen if we had written:

sum xs                  =  foldl (+) 0 xs

because the restriction only applies to pattern bindings.

The Gentle Introduction To Haskell
back next