One More on Haskell
Posted by Daniel Lyons Fri, 28 Sep 2007 09:11:00 GMT
My faith in Haskell has wavered somewhat lately, and all because of Reddit. Essentially, three things:
- Haskell’s fixed-point combinator,
y f = f (y f). - The ramifications of Haskell’s purity on abstraction.
- Haskell’s epidemic academia.
The first is really two problems. One is that the usual definition of the Y-combinator is not itself recursive. Obnoxious. The second problem is that, though it’s beautiful, there are a number of problems that you can’t express in Haskell using the above combinator which you could in an untyped language. This isn’t, apparently, news to anyone but me. I can’t see how to use the above Y-combinator. This is an incredibly academic problem, like most Haskell problems, but it bugs me a little.
The second was elucidated by Peter Van Roy on Lambda the Ultimate’s forum:
“True state lets information pass “underground” between two interfaces, i.e., the information passes without any apparent connection between them. This is because the connection is the shared state, which is shared by the two interfaces yet hidden from the outside. The shared state is a kind of covert information channel: it lets a module pass information to other modules (or to itself in the future) without anybody else seeing it.”
His point has to do with the fact that Haskell’s purity means that in order to get data from some point A to some point B through a number of other modules, each of the intermediate modules will have to carry the information around, even if it doesn’t do anything with it.
I would like to hate that, really, I would. But I can’t, because I programmed Voltaire in a very functional and abstract way in PHP partly because I could count on a handful of global variables passing some state around “underground” between modules that were loosely connected. In particular, Voltaire creates a region context and a template context in which each script is evaluated. The database connection is also shared clandestinely like this. If I were using Haskell, every function in the system would have to take an extra four parameters to get the current region, template, path and database connection, even if it wasn’t going to use it or pass it directly to a child.
It’s easy to denounce. At the same time I feel blameless for having done it, because while the state is “available” to anything beneath, nothing should be changing these variables outside the core. It’s available in a read-only way. Later on I wrote a plugin for rendering templates programmatically, and that involved understanding the inner state, and another plugin for producing lists which also involved understanding the inner state. But languages like Lisp provide interesting semantics for those times when you would want to change the behavior of a global variable safely.
Which brings me back around to Lisp, the language I have paid the least attention to of the four or so that I decided were “safe” so long ago when I started to force myself to use functional programming. And, truly, there are things about Lisp which are hard to love. The principle advantage of Lisp is that it permits you syntactic innovation. It’s certainly the only language that provides it meaningfully (let us not quibble about Lisp/Scheme differences or bizarre languages like Pliant). But doesn’t this seem like the fundamental abstraction of programming languages?
Aren’t we always starting with some problem and selecting a language based on its syntactic abstraction of some part of the problem domain? I mean, I pick Lisp because I want the ability to create my own structures. That comes in handy against every problem domain. But if I want to write a blog, well, Rails makes it a lot easier up front. Maybe I don’t get linguistic abstraction, but the starting abstraction is quite close. Michael chooses REALbasic for application development, I choose Cocoa. Both of us are making certain sacrifices in the name of abstraction. I lose defmacro. He loses the most recent Cocoa innovations. If I pick Lisp for my blog, then I’m sacrificing the fast start. The laziness of later syntactic abstraction had better pay off. The up-front cost is higher. For any given reasonable language, there is going to be a situation in which its abstraction is an up-front benefit that beats the up-front cost of using Lisp.
You see, these languages are compression algorithms. They compress your explanation of how to solve a problem. Like any compression algorithm, there are problems for each language that compress so badly you wind up writing a different language inside the language. This is Greenspun’s law all over again. Lisp wins frequently because it makes it easy to create a sublanguage for a subproblem that compresses it better. Ruby wins frequently because it encompasses most problem domains.
And Haskell wins frequently in academia because academia is interested in representative subproblems rather than complete problems. My experience on the ICFP this past year was that when you give Haskell a pure math problem, it will beat nearly anything else hands-down for both readability and speed. Now sprinkle some I/O on the problem. How about a non-local return? Perhaps a bit of, dare I say it, destructive state? Suddenly you find yourself looking up “monad transformer,” wondering what the hell you got yourself into.
And Justin, G-d bless him, is a Haskell wizard. I can sort of read the code he wrote. My contribution to that code was negligible. I wasn’t much help debugging it. But at the end, we had a performant Haskell version using prominent Haskell performance themes. But it was also 300 lines of code. I remain convinced against any of that pesky proof stuff that an OCaml implementation would have been half the length, more understandable, maintainable and debuggable. I believe Justin came away from the contest with the opposite conclusion: proof, solid proof, that Haskell can be made to attack the kinds of domains that other languages are generally selected for. In other words, his success with Haskell encourages him to use it more, whereas it filled me with doubt.
And today I see on Reddit a link to the paper introducing Haskell Server Pages. Not to the code itself, or the documentation talking about it, or to a page talking about it or some examples, but to an academic paper about it. I write on Reddit that “I have to admit I’m getting a bit tired of seeing things that should be cool Haskell libraries show up as academic papers. It’s as though the whole industry has turned its gaze to Haskell, exasperatedly asking to see something practical, and instead of turning out practical things, the Haskell world turns out academic papers about practical things. “Proof” that Haskell can be used practically is not the same as people using Haskell practically, nor the same as people practically deciding to use Haskell.”
Let’s face it. I really enjoy Haskell in part because of its snobbery, but that’s really a part of what it is. And the posturing about how mind-expanding it is. Haskell is addicted to academia. You’re much more likely to see a paper about some neat library than a neat library. When you go look at the code, it’s untested, only works on one platform, is broken or partially implemented. Academics are not rewarded for having good, useful code. They’re rewarded for writing papers.
Look at the Wash page and tell me who’s going to use this framework. Look at the first four links. Notice these PDFs and PS files are all LaTeX output. What kind of web people would do this? Academic web people.
So here we are. I suppose every language has its vices. I’m particularly drawn to languages that do a lot of posturing, apparently. Beyond that I’m not sure what to conclude. Every language that isn’t pure offal (Java) seems to have a place in the world. It would be better not to get too worked up about it, but it’s probably impossible.

Very well argued. applauds
As an avid Haskell enthusiast, I note the following three fundamental problems with Haskell: 1) A community so focused on academic research that it stifles the accessibility of the language to productive programmers. You mentioned this. 2) The static type system is by its nature restrictive, so it disallows certain forms of expression. You mentioned this too. 3) Managing needed state is a headache, often counter-productively so. You mentioned this too.
However there are flip sides to each problem: 1) The language is designed with intelligence and expects the same from its users. In a software world being flooded with idiots who program badly because it’s easy to program in language X, this is quite refreshing. 2) The static type system, while being restrictive in some cases, is an immense boon to code correctness in the other 90% of cases. 3) State is inherently a headache. I believe that the total headache caused by a stateful language and a stateless language like Haskell when dealing with state probably balances out. It follows some conservation law of difficulty somewhere. Although you could in principle make certain things easier by adding more direct true state manipulation, you will certainly cause other, equally horrendous, problems later.
Haskell is certainly not a magic bullet. It does some things very elegantly and other things rather awkwardly. The usual justification is that the awkward bits are probably bad practice anyway. However, in the real world that argument doesn’t hold water. You will always encounter awkwardness when doing anything productive with Haskell.
This is no different from any other language.
After the ICFP contest, I was somewhat disgusted with how difficult certain parts of the problem ended up being. I am convinced some of the difficulties, lack of readability, etc, are due to the lack of large-program experience in Haskell. I could do better now. That is somewhat discouraging, however, since it implies that the learning curve is truly daunting. Thus I would say it’s a bit of a stretch to say that I had solid proof about Haskell’s adequacy for other domains. We had proof of concept certainly, but not proof of ease. Several other teams in the ICFP contest mentioned that they had amazing luck with Haskell as being very intuitive and leading to a natural, fast solution; I take this to imply that I did things the dumb way somewhere, another slightly discouraging thought.
I am also not convinced that the solution would be easier to understand and shorter in OCaml. Many of the algorithmic headaches were not Haskell-specific. The nonlocal return is essentially a “goto” command, which is not tolerated in most higher-level languages without a headache. Destructive state is a book-keeping and concurrency headache in any language.
What may have been true is that we would have written the code faster in another language and would have been able to debug it more easily. Although we would have had a whole other class of problems to deal with, we would have been more “used to” dealing with them from prior experience. This is one area where Haskell will always be at a disadvantage: it works so differently that you have to reinvent your brain to deal with its new problems. That is also part of its charm.