||[Feb. 12th, 2008|10:26 am]
|[||Tags|||||apl, beware the geek, computers, haskell, j, language, lisp, maths, perl, programming, smalltalk, type systems||]|
I'm going to make what should be an uncontroversial statement: if you don't understand and use monads, you are at best a quarter of a Haskell programmer. A corollary of this is that, since using monad transformers is the only (or at least the approved) way to use two or more monads together, if you don't understand and use monad transformers you are at best half a Haskell programmer.
[Another corollary is that I am, at best, about an eighth of a Haskell programmer: though I understand monads well on a theoretical level, I invariably emerge defeated from any attempt to bend them to my will.]
But we'll come back to that later.
Something I've been thinking about for a while is this whole business of designing languages to make programs shorter. There are two factors here, of which only one has a good name that I'm aware of: so let's say that a language is terse if its tokens are short (
\ instead of
lambda, for instance), and concise if it requires few tokens to express the same algorithm. As antonyms, let's take sesquipedalian and verbose1: so a language is sesquipedalian if all its tokens are long (
call-with-current-continuation instead of
call/cc), and verbose if it requires more tokens than usual to express algorithms on average. Terseness depends to an extent on the programmer, but most languages have community norms about whether you should call your variables
i, and these are usually influenced by the keywords of the language itself.
These axes are independent: Perl is terse and fairly concise, Common Lisp (and to some extent Haskell) is sesquipedalian and concise, C is terse and verbose, and Java (everyone's favourite whipping boy) is sesquipedalian and fairly verbose (but nothing like as badly as, say, COBOL). Python is approximately as concise as Perl, but less terse. It's often remarked that APL, J etc are extremely terse; what's often overlooked is that they're also extremely concise. In my recent post about code-walking in various languages, the K solution was the shortest, even after the tokens were written out as full words to correct for the effects of terseness. This is fairly typical, in my limited experience.
That's an interesting data point - it would be interesting to know why. Let's compare them to some other concise languages to see if there's a common theme. The two concise languages that immediately spring to (my) mind are Lisp and Smalltalk2. What makes them concise? The obvious answer is "metaprogramming", for which Lisp and Smalltalk are famous, but there's another answer: they're both monistic languages. In Smalltalk, everything is an object. In Lisp, everything (well, almost everything) is an s-expression. And it works for the APL family too: in APL, everything is an array3. This means that you're dealing with a monoid of types and functions rather than a category, and monoids are much easier to deal with. My guess is that that makes it a lot easier to come up with a small, orthogonal, reusable set of operators, out of which your programs can be constructed simply. If the function you need is in the standard library, you don't have to write it. Now compare that with a language like Pascal: up until a minute ago, the type you're using didn't exist, so you need to write all the code to manipulate it. And since it's a one-off, you're probably not going to spend the time polishing your operator set until it's optimal4.
Is monistic design always a good thing? No - think about B, which had only one type (the machine word). Representing all your data as raw machine words was, by all accounts, a pain in the neck, which is why C was developed. But representing your data as s-expressions or arrays or objects is usually fairly easy. From this point of view, the concision of the APL family is not a mystery: Iverson chose a highly expressive basic structure for his single type, and a damn-near-optimal set of operators for it. I actually have a paper sitting in my "to read" folder called "About the Completeness of APL": not completeness in the sense of Turing completeness, but in the sense that any generic folding/spindling/mutilating of arrays can be expressed as a finite sequence of APL operators (I don't know if, or how, that's a stronger result than Turing completeness, which is why the paper's in my "to read" folder).
Of course, no single choice of data representation can be the One True Thing: sometimes you really do want a hash rather than an alist, or a record with named slots rather than a pair of arrays. But a good choice can get you a long way, and a language that makes the right things easy and the rest at least possible is a major win.
Let's think about Perl. Everything is a scalar. Unless it's an array. Or a hash. Or a glob. OK, that's four types: we can live with that. And globs are pretty rare anyway. We can set up a reasonable category of basic operators shuffling between three points, and the extra representational flexibility is handy. Note, incidentally, that Paul Graham has gone down this route with Arc, which has built-in hashes (which always seemed like second-class citizens in previous Lisps - correct me if I'm wrong, please).
Now let's think about Haskell again. At first sight, Haskell falls into the Pascal trap: programmers are encouraged to declare their own types for everything, and strong typing and lack of reflection makes it hard to write generic operators that work for many types5. And yet the language is fairly concise. Huh? Is everyone just ignoring "good" practice and doing everything with lists, like they were using a crippled version of Lisp? Well, yes, and it's noticeable how good Haskell's list-manipulation libraries are, but there's more to it than that.
Here's what I think is going on. Like I said above, you are at best a quarter of a Haskell programmer if you don't understand and use monads. But what is a monad? Well, as a category theorist6 I think of monads as (a generalisation of) algebraic theories - that is, as APIs (I have a post about this idea half-written). As a programmer, the interesting thing about monads is not that you can swap them in and out when you want to change your model of computation half-way through development (I'd love to hear of even a single case where that's happened) - it's the fact that you can write useful utilities like
liftM2 and friends, that work across all monads. A simple, orthogonal, highly reusable set of operators, abstracted across APIs.
So I think the Haskell people are doing something very interesting: they've pushed the monism up a level. Everything is a List or a Maybe or an IO or a Hoojimaflip or an AquaTeenHungerForce or whatever: but all of those things are monads. So you get the orthogonality benefits of monism, without the loss of representational flexibility. You have to write a bit more code when you declare your datatypes, but maybe this can be a good trade-off.
An onager, by the way, is a wild ass.
1 There really ought to be a word that means "would never use a twopenny word when a half-crown word would do", but I can't think of one. English grads? Edit: sesquipedalian! Of course! Thanks, fanf! (Originally, I used "prolix")
2 I actually came up with this list by thinking about languages whose users were the most passionate. But they're also extremely concise, which I think is a large part of the reason for the passion. If I were focusing purely on concision, I should probably consider Forth, but I don't know enough about it.
3 J has "boxed arrays" too, which are something like two-dimensional s-expressions, but let's leave those aside for now.
4 You might want to raise this objection against Smalltalk, too: objects are members of classes, which are something like types. Now, I've hardly used Smalltalk, so I'm probably talking out of my elbow, but: since everything is an object, and the language has powerful reflection features and duck typing, we can in fact write generic operators that work for objects of many or all classes. But maybe I'm entirely wrong about Smalltalk programming: in which case, please delete all references to the language from my argument.
5 Do you find yourself wanting to go out and strangle small fluffy animals every time you have to type out an instance declaration that would be entirely unnecessary in a duck-typed language? I do. Particularly when it doesn't work and spits out some ludicrous error message at me, telling me that I've encountered another stupid corner case of the typeclass system.
6 I learned to my surprise the other day that I'm a member of the Geometry and Topology research group, and not the algebra research group as I'd always assumed - apparently universal algebra is now considered a branch of geometry!