Mike Vanier (mvanier) wrote,
Mike Vanier

Haskell is sandbox crack!

I've been spending my Copious Free Time lately trying to implement a simple dynamically-typed language in Haskell, both because I like this kind of programming and to make me a better Haskell programmer. Learning Haskell isn't like learning most other programming languages. With most programming languages, you work with the language for a few weeks or months and then feel pretty confident that you understand most of what's going on. Sure, there are idioms and design patterns you don't know, but you can pick those up as you go along. In contrast, with Haskell you stumble along in a fog, then find a map that gives you a clue which way to go, then stumble a bit further, then realize that although you got where you wanted to go, you could have gotten there much more easily using a different path, then you backtrack and try again, then you find another map, etc. etc. In other words, even when you know how to do something you keep finding better ways of doing the same thing.

This is extremely time-consuming and frustrating, and yet I continue to do it. Why?

One reason is obvious: Haskell is a pure functional language. And that means that Things Just Work in a Sensible Way. It also means that things compose nicely, so you can keep stacking abstractions on top of abstractions, and removing all the repeated code to build better abstractions. So you can program at a very high level. Well-written Haskell programs seem almost stratospheric in their abstraction level compared to well-written programs in other languages.

There's another reason that is less obvious: Haskell is sandbox crack. What I mean by this is that what Haskell does better than any other language I know of is to allow programmers to define isolated computational sandboxes that handle some aspect of the overall computational work. This is what Haskell monads are mainly used for. So instead of allowing functions to do arbitrary input/output (and thus break functional purity), Haskell provides you with the IO monad and tells you that all of your input/output must be done in the context of that monad. So now you have a clean separation between pure functions (that don't do any I/O, and thus that Behave Nicely), and those that do. BUT: once you get a taste for this sort of thing, you become addicted to it, and you're never satisfied. Suddenly you say "My application has to read from an input stream, but it doesn't have to write to an output stream; why should I use the IO monad when I'm not going to be writing anything?" So you use the Reader monad instead. If you just need to write and not read, you use the Writer monad. You can use IO to give you mutable variables with imperative update, but if you don't actually have to do input/output, why use IO (which is more general than you want)? You can use the ST monad instead. Or if you only have a couple of small piece of state, why use the ST monad, which handles arbitrary amounts of state (and is implemented in terms of the IO monad anyway, which I find kind of ugly). So you use the State monad, which only handles a fixed amount of state. And on and on. Each new sandbox allows you to make stronger statements about what your code is allowed to do, and more importantly, what it isn't allowed to do. Different parts of your program need different sandboxes, and if some parts need more than one you can use monad transformers to add up sandboxes to make larger sandboxes. And all is well in the world (or should I say the RealWorld?).

Then eventually you reach a situation where the sandboxes that Haskell provides you aren't quite sufficient to give you the sandbox you want, and you post angry emails to haskell mailing lists saying something like "Goddamn it! What I need are third-order existential abstract generalized monadoids, and I cannot believe that Haskell doesn't support this natively!" And you're serious, and you wonder why nobody else takes you seriously. And if you're Oleg, you write an article showing how Haskell's type classes can already support third-order existential abstract generalized monadoids, but there is an obscure corner case in which they may break some natural invariant. And if you're not Oleg, you try to understand what Oleg wrote and eventually get about 20% of it. And then you use some workaround which would be considered incredibly elegant in any other language but which breaks your heart because you can't make your sandbox quite as small as you want it to be, so you feel like a coding slob. You're embarrassed to show anyone else your code, but it does work.

And this is a good thing. Because this is part of the process by which programming languages will become what they need to become. But nobody said that it was going to be pleasant.
  • Post a new comment


    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded