correct • elegant • free

△ Why Haskell Is My Favourite Programming Language △

Equals ▻


or, The Hindley-Milner Polymorphic Type System

Haskell is strongly-typed. In fact, I would go so far as to say that Haskell is very strongly-typed. Every object has a type associated with it, and the compiler will refuse to accept a program till all the types line up.

Now, among the hacker community, strong typing is often seen as a Bad Thing in a programming language. I think there are two strands to this. First, the need to declare types slows down the business of programming, and particularly prototyping. Secondly, types don't seem to buy all that much. Neither of these objections applies to Haskell.

Type Inference

In Haskell, you almost never need to declare a type. Instead, the compiler is able to infer the type of everything. This means that Haskell's strong typing comes at very little cost to the programmer: less than declaring my variables in Perl with use strict!

For a very simple example, consider ascii.hs, which converts a string into a list of decimal ASCII values.

import Data.Char

asciiList str = map ord str

Since ord :: Char -> Int (meaning ord is a function that accepts one argument of type Char and returns an Int), and map applies a function to each element of a list, the type of asciiList must be asciiList :: [Char] -> [Int]: it takes a list of characters, and returns a list of ints. The compiler needs no help in deducing this:

*Main> :type ord
ord :: Char -> Int
*Main> :type asciiList
asciiList :: [Char] -> [Int]
*Main> asciiList "Hello, world!\n"

And the type of map? That's map :: (a -> b) -> [a] -> [b]. Concrete types, such as Char and Int, always start with a capital. So a and b are here type variables. In words, the first argument of map is (a -> b), a function that itself takes an argument of any type a and returns any type b; the second argument of map is a list of as; and the return type is a list of bs. (It probably seems peculiar that -> is also used between the types of the two "inputs" (arguments) to map, as well as before the type of its "output" (return value), but this is actually a beautiful feature, called "currying", that I won't be going into here.)

Rich Types

So, type inference gives us strong typing for free. But is strong typing all that great? For sure, in languages like C and Java the compiler can catch a few errors, such as passing the wrong type to a function. But Haskell has much richer types than those languages.

One example will give us a hint of the flavour. Consider optional values. In C, an int is always an int, but more complicated types, even strings, are represented by pointers, and a pointer can be null. One unpleasant implication of this is that strlen() can dump core. Similarly, Java has nil, and Perl extends optionality to everything with undef, as does SQL with NULL.

Haskell isn't like this. A String is always a string, and it always has a length (well, unless it's infinite, but we won't get into that yet). Same for lists, records, etc. So what happens if you need an optional value? There is a standard type constructor Maybe for just this case.

My favourite example of the way Maybe works is with Booleans. Naturally, Haskell has a Bool type, with just two possible values: False and True. The type Maybe Bool, though, has three possible values: Just False, Just True, and Nothing.

And Maybe can be applied to absolutely any type, extending it with one extra (non-)value. So to model a C char * in Haskell you could use the type Maybe String, and represent any actual string, like "foo", as Just "foo", and the null pointer as Nothing. As you'd expect, Haskell interfaces to relational databases use Maybe types for columns that lack the NOT NULL constraint.

What's the point of all this?

△ Why Haskell Is My Favourite Programming Language △

Equals ▻