Haskell/Type basics II

In this chapter, we will show how numerical types are handled in Haskell and introduce some important features of the type system. Before diving into the text, though, pause for a moment and consider the following question: what should be the type of the function ?

The class
Mathematics puts few restrictions on the kinds of numbers we can add together. Consider, for instance, $$2 + 3$$ (two natural numbers), $$(-7) + 5.12$$ (a negative integer and a rational number), or $$\frac{1}{7} + \pi$$ (a rational and an irrational). All of these are valid. In fact, any two real numbers can be added together. In order to capture such generality in the simplest way possible we need a general  type in Haskell, so that the signature of   would be simply

However, that design fits poorly with the way computers perform arithmetic. While computers can handle integers as a sequence of binary digits in memory, that approach does not work for real numbers, thus making a more complicated encoding necessary for dealing with them: floating point numbers. While floating point provides a reasonable way to deal with real numbers in general, it has some inconveniences (most notably, loss of precision) which makes using the simpler encoding worthwhile for integer values. So, we have at least two different ways of storing numbers: one for integers and another for general real numbers. Each approach should correspond to different Haskell types. Furthermore, computers are only able to perform operations like  on a pair of numbers if they are in the same format.

So much for having a universal  type – it seems that we can't even have   mix integers and floating-point numbers. However, Haskell can at least use the same  function with either integers or floating point numbers. Check this yourself in GHCi:

When discussing lists and tuples, we saw that functions can accept arguments of different types if they are made polymorphic. In that spirit, here's a possible type signature for  that would account for the facts above:

With that type signature,  would take two arguments of the same type   (which could be integers or floating-point numbers) and evaluate to a result of type   (as long as both arguments are the same type). But this type signature indicates any type at all, and we know that we can't use  with two   values, or two   values. What would adding two letters or two truth-values mean? So, the actual type signature of  uses a language feature that allows us to express the semantic restriction that   can be any type as long as it is a number type:

is a typeclass — a group of types — which includes all types which are regarded as numbers. The  part of the signature restricts   to number types – or, in Haskell terminology, instances of.

Numeric types
So, which are the actual number types (that is, the instances of  that the   in the signature may stand for)? The most important numeric types are ,  and  :


 * corresponds to the plain integer type found in most languages. It has fixed maximum and minimum values that depend on a computer's processor. (In 32-bit machines the range goes from -2147483648 to 2147483647).


 * also is used for integer numbers, but it supports arbitrarily large values – at the cost of some efficiency.


 * is the double-precision floating point type, a good choice for real numbers in the vast majority of cases. (Haskell also has, the single-precision counterpart of  , which is usually less attractive due to further loss of precision.)

Several other number types are available, but these cover most in everyday tasks.

Polymorphic guesswork
If you've read carefully thus far, you know that we don't need to specify types always because the compiler can infer types. You also know that we cannot mix types when functions require matched types. Combine this with our new understanding of numbers to understand how Haskell handles basic arithmetic like this:

This may seem to add two numbers of different types – an integer and a non-integer. Let's see what the types of the numbers we entered actually are:

So,  is neither   nor  ! Rather, it is a polymorphic value, which can "morph" into any number type. Now, let's look at the other number:

is also a polymorphic value, but one of the  class, which is a subset of   (every   is a , but not every   is a  ; for instance,  s and  s are not  ).

When a Haskell program evaluates, it must settle for an actual matching type for the numbers. The type inference accounts for the class specifications:  can be any , but there are extra restrictions for  , so that's the limiting factor. With no other restrictions,  will assume the default   type of , so   will become a   as well. Addition then proceeds normally and returns a.

The following test will give you a better feel of this process. In a source file, define

Then load the file in GHCi and check the type of. Then, change the file to add a  variable,

reload it and check the types of  and. Finally, modify  to

and see what happens with the types of both variables.

Monomorphic trouble
The sophistication of the numerical types and classes occasionally leads to some complications. Consider, for instance, the common division operator. It has the following type signature:

Restricting  to fractional types is a must because the division of two integer numbers will often result in a non-integer. Nevertheless, we can still write something like

because the literals  and   are polymorphic values and therefore assume the type   at the behest of. Suppose, however, we want to divide a number by the number of elements in a list. The obvious thing to do would be using the  function, which takes a list and gives the number of elements in it:

Unfortunately, that blows up:

As usual, the problem can be understood by looking at the type signature of :

For now, let's focus on the type of the result of. It is ; the result is not polymorphic. As an  is not a , Haskell won't let us use it with.

To escape this problem, we have a special function. Before following on with the text, try to guess what this does only from the name and signature:

takes an argument of some  type (like   or  ) and makes it a polymorphic value. By combining it with, we can make the length of the list fit into the signature of  :

In some ways, this issue is annoying and tedious, but it is an inevitable side-effect of having a rigorous approach to manipulating numbers. In Haskell, if you define a function with an  argument, it will never be converted to an   or , unless you explicitly use a function like. As a direct consequence of its refined type system, Haskell has a surprising diversity of classes and functions dealing with numbers.

Classes beyond numbers
Haskell has typeclasses beyond arithmetic. For example, the type signature of  is:

Like  or ,   is a polymorphic function. It compares two values of the same type, which must belong to the class  and returns a. is simply the class for types of values which can be compared for equality, and it includes all of the basic non-functional types.

A quite different example of a typeclass that we have glossed over has appeared in the type of. Given that  takes a list and gives back an , we might have expected its type to be:

The actual type, however, is a little trickier:

Beyond lists, there are other kinds of structures that can be used to group values in different ways. Many of these structures (together with lists themselves) belong to a typeclass called. The type signature of  tells us that it works not only with lists, but also with all those other   structures. Later in the book, we will see examples of such structures, and discuss  in detail. Until then, whenever you see something like  in a type signature, feel free to mentally replace that with.

Typeclasses add a lot to the power of the type system. We will return to this topic later to see how to use them in custom ways.