evan_tech

Previous Entry Share Next Entry
02:55 am, 25 Jul 06

icfp wrapup

For the contest, I wrote code in: Haskell, C/C++, and a Ruby five-liner. (And other languages that occurred within the problems: a Basic variant and an ML variant...)

Here are some of my main observations.

Using Haskell for a time-limited competition was nicer than I expected.
At one point I needed to parse s-expressions, and I could write it out in a neat six lines of code. At another point I started building a tree data structure and then realized there's a builtin tree module along with pretty-printing functions. And again it's a single line of code to convert a tree of objects into an equivalent tree of strings. (Yay for abstractions!)

I've really noticed this about the Haskell standard library: it's severely lacking from the Python-style "a module for everything" perspective, with no HTTP or config file parsers, etc. This is indeed a real problem. But they instead focus on more nuts-and-boltsy programming abstractions, like data structures, pretty-printing, and flow control, and that I hadn't appreciated how powerful that really is. (This goes back to a post I wrote maybe a year ago on Haskell, about how the language and standard library are hard to disentangle. Maybe I'll rewrite it at some point now that I'm more knowledgeable about it.) In particular, Haskell has a lot of control-flow related libraries that I think simply aren't implementable in other languages.

Here's a story. At one point I really wished I had something that was stateful (recursing through a tree but ticking off items from a global list). I switched to using the State monad, which lets you simulate state. Then it turned out that at some steps in the computation I needed to make a choice between seemingly-equivalent options, but that each choice would have different effects later and so I needed to compute all possible trees of choices to find the one that would ultimately complete. It was then easy to switch out the state monad and even switch to "grab all possible solutions to this in a list", both because changing the types of the functions made the compiler point out where all the changes were needed, and also because it wasn't actually that different in its implementation. As I was fixing the code I was thinking to myself about how awful it would've been if I had implemented it with real mutable data structures, like I would in C++... would I have made copies of the data structure each time I recursed, or what?

Contrasting with C++
Someone anonymously made this remark (I can't tell if it was intended to be a wisecrack or an honest remark) on a previous post: I aprove of an FP contest which so vividly illustrates the drawbacks of FP languages up front.
Depending on your interpretation, this is both apt and silly. For the core virtual machine implementation, it (in retrospect) was obviously a good candidate for C: speed-critical, well-understood and small problem. From the mailing lists, it sounds like other teams had trouble making their implementation fast enough.

However, for other parts of the competition, I was repeatedly surprised at how much more productive I was not using C (or C++*). (I write this as a person who writes C++ professionally full-time, but also as a person with no experience with programming competitions, so take with a grain of salt.) There's two sides to this.

On one hand, C involves a lot of futzing around with declarations and memory management. Everyone complains about this sort of thing, but seeing the productivity difference side by side really was striking. I think Java is just as bad in this respect -- they fixed the memory management but screwed the rest.

On the other, I think it would've been a disaster (at least for me) to try to use a dynamically-typed language. I was creating and tearing down complicated data structures (like a list of a list of requirements, which were themselves pairs of strings and lists of items, which were...) and changing their implementations rapidly, and without the compiler's help I would've been lost.

I still think there's a good niche waiting for a langauge that's as expressive as Haskell but without all the functional-programming craziness. It was almost O'Caml but now that I've been exposed to type classes I don't think I can go back. (And, speaking as an experienced O'Caml programmer, its syntax is really bad.)

On the other hand, the functional-programming craziness is part of what makes Haskell so good. Once you're in the zone it really just sorta flows, like you feel the code as a mesh of interdependencies instead of a sequence of commands...

Limitations
The main thing that caught me is that it can be very difficult to debug Haskell code. Since printing is an effect, it's not allowed in most functions. There's a "Debug trace" function that takes a string (the text to display) and an argument (the thing to wrap), and it dumps the string when the argument is evaulated. But since Haskell is lazy, it's very difficult to be sure when an argument is evaluated!

This would all be helped by a real debugger, and I guess people are working on that. But even then, look at this trace... the laziness is still confusing.