Haskell is a strongly typed language. In fact, the type system in Haskell is both stricter and more
expressive than any type system I’ve seen for any non-functional language. The moment we get beyond
writing trivial integer-based functions, the type system inevitably becomes visible, so we need to
take the time now to talk about it a little bit, in order to understand how it works.
One of the most important things to recognize about Haskell’s type system is that it’s based on *type
inference*. What that means is that in general, you *don’t* need to provide type declarations. Based
on how you use a value, the compiler can usually figure out what type it is. The net effect is that
in many Haskell programs, you don’t write *any* type declarations, but your program is still
carefully type-checked. You *can* declare a type for any expression you want, but the only time you
*must* is when something about your code is ambiguous enough that type inference can’t properly
select one type for an expression.
The type inference system works on the principle of *most general type*; that is, when it’s inferring
a type for an expression, it will always pick the most general, inclusive type that can match the
expression. And that leads to one complication for beginners: Haskell’s type system is almost two
different type systems – a basic type system, and a *meta*-type system. The meta-type system is based on something called *type classes*, which group related families of types together.
The most general types that come up as a result of type inference are frequently based on type classes, rather than on specific concrete types. A type-class in code looks sort of like a *predicate* over a type variable. For example, the type of
the parameters to the “`+`” operator must be a numeric type. But since “`+`” can be used on integers,
floats, complex, rationals, and so on, the type of the “`+`” operator’s parameters needs to be
something that includes all of those. The way that Haskell says that is the type is “`Num a => a`”.
The way to read that is “Some type ‘a’ such that ‘a’ is a numeric type.”.
The thing to remember is that essentially, a type-class is a type for types. A *type* can be thought
of as a predicate which is only true for members of that type; a *type-class* is essentially a
predicate over *types*, which is only true for types that are members of the type-class. What
type-classes do is allow you to define a *general concept* for grouping together a family of
conceptually related types. Then you can write functions whose parameter or return types are formed
using a type-class; the type class defines a *constraint* over a group of types that could be used in
the function. So if we write a function whose parameters need to be numbers, but don’t need to be a
*specific kind* of number, we would write it to use Haskell will infer types based on the “`Num`”
type-class: “`Num`” is the most general type-class of numbers; the more things we actually *do* with
numbers, the more constrained the type becomes. For example, if we use the “/” operator, instead of
inferring that the type of parameter must be an instance of the “Fractional” type-class.
With that little diversion out of the way, we can get back to talking about how we’ll use types in
Haskell. Types start at the bottom with a bundle of *primitive* atomic types which are built in to
the language: `Integer`, `Char`, `String`, `Boolean`, and quite a few more. Using those types, we can
*construct* more interesting types. For now, the most important constructed type is a *function
type*. In Haskell, functions are just values like anything else, and so they need types. The basic form of a simple single-parameter function is “`a -> b`”, where “`a`” is the type of the parameter, and “`b`” is the type of the value returned by the function.
So now, let’s go back and look at our factorial function. What’s the type of our basic “`fact`”
function? According to Hugs, it’s “`Num a => a -> a`”.
Definitely not what you might expect. What’s happening is that the system is looking at the
expression, and picking the most general type. In fact, the only things that are done with the
parameter are comparison, subtraction, and multiplication: so the system infers that
the the parameter must be a *number*, but it can’t infer anything more than that. So it says that the type of the function parameter is a numeric type; that is, a member of the type-class “`Num`”; and that the return type of the function is the same as the type of the parameter. So the statement
“`Num a => a -> a`” basically says that “`fact`” is a function that takes a parameter of *some* type represented by a *type variable* “`a`” and returns a value of the *same* type; and it also says that the type variable “`a`” must be a member of the meta-type “`Num`”, which is a type-class which
includes all of the numeric types. So according to Haskell, as written the factorial function is a function which takes a parameter of *any* numeric type, and returns a value of the *same* numeric type as its parameter.
If we look at that type, and think about what the factorial function actually does, there’s
a problem. That type isn’t correct, because factorial is only defined for integers, and if we pass
it a non-integer value as a parameter, it will *never terminate*! But Haskell can’t figure that
out for itself – all it knows is that we do three things with the parameter to our function: we
compare it to zero, we subtract from it, and we multiply by it. So Haskell’s most general type for
that is a general numeric type. So since we’d like to prevent anyone from mis-calling factorial by
passing it a fraction (which will result in it never terminating), we should put in a type
declaration to force it to take the more specific type “`Integer -> Integer`” – that is, a function
from an integer to an integer. The way that we’d do that is to add an *explicit type declaration*
before the function:
fact :: Integer -> Integer
fact 0 = 1
fact n = n*fact(n-1)
That does it; the compiler accepts the stronger constraint provided by our type declaration.
So what we’ve seen so far is that a function type for a single parameter function is created from two
other types, joined by the “`->`” type-constructor. With type-classes mixed in, that can be
*prefixed* by type-class constraints, which specify the *type-classes* of any type variables in the
Before moving on to multiple parameter functions, it’s useful to introduce a little bit of syntax.
Suppose we have a function like the following:
poly x y = x*x + 2*x*y + y*y
That definition is actually a shorthand. Haskell is a lambda calculus based language,
so semantically, functions are really just lambda expressions: that definition is really just a binding from a name to a lambda expression.
In lambda calculus, we’d write a definition like that as:
>poly ≡ λ x y . x\*x + 2\*x\*y + y\*y
Haskell’s syntax is very close to that. The definition in Haskell syntax
using a lambda expression would be:
poly = (\ x y -> x*x + 2*x*y + y*y)
The λ in the lambda expression is replaced by a backslash, which is the
character on most keyboards that most resembles lambda; the “.” becomes an arrow.
Now, with that out of the way, let’s get back to multi-parameter functions. Suppose we take the poly function, and see what Hugs says about the type:
poly x y = x*x + 2*x*y + y*y
> Main> :type poly
> poly :: Num a => a -> a -> a
This answer is very surprising to most people: it’s a *two* parameter function. So intuitively, there
should by *some* grouping operator for the two parameters, to make the type say “a function that
takes two a’s, and returns an a”; something like “`(a,a) -> a`”.
But functions in Haskell are automatically *curried*. Currying is a term from mathematical logic; it’s based on the idea that if a function is a value, then you don’t *ever* need to be able to take more than one parameter. Instead of a two parameter function, you can write a one-parameter function that returns another one-parameter function. Since that sounds really confusing, let’s take a moment and look again at our “poly” example:
poly x y = x*x + 2*x*y + y*y
Now, suppose we knew that “x” was going to be three. Then we could write a special one-parameter function:
poly3 y = 3*3 + 2*3*y + y*y
But we *don’t* know “`x`”. But what we *can* do is write a function that takes a *parameter* “`x`”, and returns a function where all of the references to `x` are filled in, and given a y value, will return the value of the polynomial for x and y:
polyn x = (\y -> x*x + 2*x*y + y*y)
If we call “`polyn 3`”, the result is exactly what we wrote for “`poly3`”.
If we call “`polyn a b`”, it’s semantically *exactly* the same thing as “`poly a b`”. (That doesn’t mean that the compiler actually *implements* multi-parameter functions by generating single-parameter functions; it generates multi-parameters functions the way you’d expect. But everything *behaves* as if it did.) So what’s the type of `polyn`? Well, it’s a function that takes a parameter of
type `a`; and returns a *function* of type “`Num a => a -> a`”. So, the type of `polyn` is
“`Num a => a -> (a -> a)`”; and since the precedence and associativity rules are set up to make currying convenient, the parents in that aren’t necessary – that’s the same as “`Num a => a -> a -> a`”. Since “`poly`” and “`polyn`” are supposed to be semantically equivalent, that means that “`poly`”‘s type must also be “`Num a => a -> a -> a`”.