Professional Documents
Culture Documents
So let's dive right in a take a look at a very simple Haskell definition of the
factorial function:
fact n = if n == 0
then 1
else n * fact (n - 1)
fact 0 = 1
fact n = n * fact (n - 1)
In the semantics of Haskell, those two are completely equivalent. In fact, the
deep semantics of Haskell are based on pattern matching - so it's more correct to
say that the if/then/else version is translated into the pattern matching version
than vice-versa! In fact, both will basically expand to the Haskell pattern-
matching primitive called the "case" statement, which selects from among a list of
patterns: (Note: I originally screwed up the following, by putting a single "="
instead of a "==" inside the comparison. Stupid error, and since this was only
written to explain things, I didn't actually run it. Thanks to pseudonym for
pointing out the error.)
Another way of writing the same thing is to use Haskell's list type. Lists are a
very fundamental type in Haskell. They're similar to lists in Lisp, except that
all of the elements of a list must have the same type. A list is written between
square brackets, with the values in the list written inside, separated by commas,
like:
[1, 2, 3, 4, 5]
As in lisp, the list is actually formed from pairs, where the first element of the
pair is a value in the list, and the second value is the rest of the list. Pairs
are constructed in Haskell using ":", so the list could also be written in the
following ways:
1 : [2, 3, 4, 5]
1 : 2 : [3, 4, 5]
1 : 2 : 3 : 4 : 5 : []
[ x .. y ]
Getting back to our factorial function, the factorial of a number "n" is the
product of all of the integers from 1 to n. So another way of saying that is that
the factorial is the result of taking the list of all integers from 1 to n, and
multiplying them together:
listfact n = listProduct [1 .. n]
But that doesn't work, because we haven't defined listProduct yet. Fortunately,
Haskell provides a ton of useful list functions. One of them, "foldl", takes a
function f, an initial value i, and a list [l1, l2,...,ln], and basically does
f(ln(...f(l2, f(i,l1)))). So we can use foldl with the multiply function to
multiply the elements of the list together. The only problem is that
multiplication is written as an infix operator, not a function. In Haskell, the
solution to that is simple: an infix operator is just fancy syntax for a function
call; to get the function, you just put the operator into parens. So the
multiplication function is written "(*)". So let's add a definition of listProduct
using that; we'll do it using a where clause, which allows us to define variables
or functions that are local to the scope of the enclosing function definition:
listfact n = listProduct [ 1 .. n ]
where listProduct lst = foldl (*) 1 lst
I need to explain one more list function before moving on. There's a function
called "zipWith" for performing an operation pairwise on two lists. For example,
given the lists "[1,2,3,4,5]" and "[2,4,6,8,10]", "zipWith (+) [1,2,3,4,5]
[2,4,6,8,10]" would result in "[3,6,9,12,15]".
Now we're going to jump into something that's going to seem really strange. One of
the fundamental properties of how Haskell runs a program is that Haskell is a lazy
language. What that means is that no expression is actually evaluated until its
value is needed. So you can do things like create infinite lists - since no part
of the list is computed until it's needed. So we can do things like define the
fibonacci series - the complete fibonacci series, using:
fiblist = 0 : 1 : (zipWith (+) fiblist (tail fiblist))
This looks incredibly strange. But if we tease it apart a bit, it's really pretty
simple:
That third step is the tricky one. It relies on the laziness of Haskell: Haskell
won't compute the nth element of the list until you explicitly reference it; until
then, it just keeps around the unevaluated code for computing the part of the list
you haven't looked at. So when you try to look at the nth value, it will compute
the list only up to the nth value. So the actual computed part of the list is
always finite - but you can act as if it wasn't. You can treat that list as if it
really were infinite - and retrieve any value from it that you want. Once it's
been referenced, then the list up to where you looked is concrete - the
computations won't be repeated. But the last tail of the list will always be an
unevaluated expression that generates the next pair of the list - and that pair
will always be the next element of the list, and an evaluated expression for the
pair after it.
Just to make sure that the way that "zipWith" is working in "fiblist" is clear,
let's look at a prefix of the parameters to zipWith, and the result. (Remember
that those three are all actually the same list! The diagonals from bottom left
moving up and right are the same list elements.)
fiblist = [0 1 1 2 3 5 8 ...]
tail fiblist = [1 1 2 3 5 8 ... ]
zipWith (+) = [1 2 3 5 8 13 ... ]
Given that list, we can find the nth element of the list very easily; the nth
element of a list l can be retrieved with "l !! n", so, the fibonacci function to
get the nth fibonacci number would be:
fib n = fiblist !! n
And using a very similar trick, we can do factorials the same way:
The nice thing about doing factorial this way is that the values of all of the
factorials less than n are also computed and remember - so the next time you take
a factorial, you don't need to repeat those multiplications.