A few hours ago the ICFP Programming Contest 2007 ended, and the team Funktion im Kopf der Mensch participated again, this year slightly demotivated by the fact that the organizer’s main goal seems to have been to show off how smart they are instead of letting the participant practice their creative skills.
As always we were using Objective Caml for most of our programming work. O’Caml has always had some annoying sides, but this year I ran into so many of them that I got seriously pissed off, and will now have to investigate other options.
For us, O’Caml’s main attraction are its type system, which, when used correctly, makes it surprisingly hard to write incorrect code, and its performance. O’Caml’s big weakness is its standard library. To be frank, it sucks, big time.
I’ll present the issues I ran into during the past three days, and I’ll compare the way O’Caml handles those things with Common Lisp, because in the areas I’ll discuss here, it is a good example of how to do it right. Note that I’m not saying that Common Lisp does everything else better. There is a reason why we’ve been using O’Caml, after all.
Let’s start with numbers. Say we need a function which adds the number 3 to a given number. Should be a trivial task, right? Indeed, in Lisp it is:
(defun add-3 (x)
(+ x 3))
Let’s see if it works:
> (add-3 0)
> (add-3 4.5)
> (add-3 3/2)
> (add-3 (sqrt -4))
Great – we can give the function any kind of number, and it adds 3. How do we implement this in O’Caml? Well, first we’ll have to decide which kind of number we want to operate on, because, you see, you can’t mix different kinds of numbers. Ok, let’s concentrate on integers. There can’t be more than one kind of them, can there? Well, it turns out there are several. There’s “int”, “int32”, “int64”, “nativeint” and “big_int”. Here’s the code for all of them:
let add_3_int x =
x + 3
let add_3_int32 x =
Int32.add x (Int32.of_int 3)
let add_3_int64 x =
Int64.add x (Int64.of_int 3)
let add_3_nativeint x =
Nativeint.add x (Nativeint.of_int 3)
let add_3_big_int x =
add_big_int x (big_int_of_int 3)
And those are just for the integer types! How ridiculous can it get?
Next issue: Arrays. Let’s say we need a function which returns an array with all the elements of the array it was given in reverse order, except for the last one. A triviality in Lisp:
(defun chop-rev (seq)
(reverse (subseq seq 0 (1- (length seq)))))
Let’s see if it works:
> (chop-rev #("abc" "123" "xyz"))
> (chop-rev "abcdef")
Notice that in Lisp string are arrays, too. As an added bonus, our function even works on lists:
> (chop-rev '(1 2 3 4))
(3 2 1)
What can O’Caml do for us? Again, we first need to ask: Which kind of arrays are we talking about? There’s the built-in array type “array”. Then there’s strings, which O’Caml doesn’t treat as arrays, for no apparent reason. There’s another type called “Buffer”, which is really just a string that can grow, but is, of course, completely incompatible with “real” strings. There’s also something called “bigarray”, which is an array that can be big. In any other language such a data structure would be obsolete, because standard arrays wouldn’t have any size limits. Not so in O’Caml! You can enjoy all the comforts of the built in array type, but only if you don’t need to put in more than about 4 million elements (on 32 bit systems), unless you work with floats, in which case you’re limited to 2 million elements. Perhaps I should also mention that strings are limited to 16 million characters, but they forgot to implement big strings. What a joy!