November 18th, 2003

  • evan

nothing will ever please me / rehashing mailing lists

Albert (who was on my team in the programming competition, and is the other undergrad in my grad PL class) likes to teach me funny aspects of C++. (That pointer stuff the other day was his doing, mostly.)
Today, after yet another wart, I told him: "I hate C++. I really do." And he said: "But it has so many interesting and amusing corners!" And I said: "So does Perl, which I like and you hate; we're both inconsistent." And that reminded me of that Larry/Bjarne post.

Java annoys me for more subtle reasons that I'll get into some other time, maybe. But the collection classes were an obvious wart: you have to do a runtime downcast whenever you pull something out of them. So, everyone says, the generics stuff will fix that. But I just read:
Generics are statically checked, but because of the way they are implemented there are ways to get around the wrong type of objects into your collections after all, so the runtime checks need to be generated as well.
Yuck. But I guess it's only so the bytecode works on pre-generics JVMs?

Too much looking at Cyclone/OCaml (and C!), and too much fighting with Perl and Ruby, turns me into a static-typing snob. (More on this in sec.)
  • evan

language cruft

One of the Perlers wrote somewhere about the Law of Conservation of Cruft. The idea is simple: if your program has some parts that are necessarily ugly, there are two ways to approach it. One is to keep your language pure, and make your program ugly; this is the approach exemplified in my mind by Python and Java. The other is to make your language ugly, the approach exemplified by Perl.

A good example of the difference is subgroups in a regular expression pattern match. Both Python and Perl can match a variable against a regular expression like /^(.)(.)$/. In Perl, this ($foo =~ /^(.)(.)$/) evaluates to something nonfalse¹, and stuffs the two subgroup matches into the magic variables $1 and $2. Ugly! In Python², this (re.match('^(.)(.)$', foo)) returns a MatchObject, which you then can extract the subgroups out of via Verbose!

What's amusing to me is that Ruby supports both idioms: Perl-style, which still uses $1, $2 (which have weird magical behavior different from other Ruby variables) and Python-style (/(.)(.)/.match(foo) produces a Match object)... and in all the Ruby code I've ever seen I have never seen anybody use the second form.

These sorts of tradeoffs is so common in natural langauge that the idea has its own name (that I forget). A language like Japanese has only 8 or so consonants and 5 vowels³, so words are longer. But because a listener only has to distinguish between 5 vowels (contrast to English, which has somewhere around 12), speakers can speak significantly faster. The same tradeoffs arise across the entire spectrum of language:
  • German uses more complicated words to express complicated ideas in a single word, pushing the detail of what they mean to the speaker's lexicon. English borrows all sorts of meaningful, interesting words like zeitgeist and schadenfreude verbatim because we can't say them without using multiple pieces ("spirit of the times").
  • Arabic (only three vowels!) has such simple-looking characters that they skip vowels and have lots of diacritics. A nice thing about simple characters is that they can be much more expressive with it than we can. Check out the Arabeyes logo: that's actually legible!

In natural language, it's hard to say one design decision is better than another. (Some would argue that language naturally optimizes itself, so they're all near optimal.) Some aspects of programming languages are debatable, too, such as the above cruft balance. So where do you offload complexity? As with language, you push down in one place and it pops up in another. The simpler Perl-style rexep syntax produces magic variables. Strong typing allows you to find more errors at compile time while still compiling to fast code (think C++ templates versus Java collections), but you lose expressivity (in terms of dynamicism) and simplicity (writing out types and declarations everywhere).

I think the appeals to consistency and simplicity in programming langauge design are faulty goals. Languages like Chinese-- where (in a very real sense-- I just asked the native speaker sitting next to me) every syllable is its own word and you combine them to make more complicated concepts-- really appeal to the engineer in me, who likes the idea of building complicated things out of simple and powerful building blocks. But most languages aren't Chinese; we naturally gravitate to some (conceptually ugly) medium mix of atomic concepts and more complicated words. LISP and Smalltalk were appeals to the engineer's instinct, and while they're both useful to think about, I'll always choose a more moderate language.

Here are a number of constraints that I don't think are controversial:
  1. Programmer time is much more valuable than computer time, with one exception:
  2. Runtime programs (code) should be as fast as possible.
  3. Programs should be as terse as possible while still remaining clear: whenever you cut'n'paste you introduce potential errors and more code to read for a successor.
  4. As many errors as possible should be detected at compile time.

There's a place you can offload the hardness of these problems for free: the compiler. You write a fancy compiler once, and all future programs benefit. (Compiler complexity is of course tied to language complexity, because depending on how the language is designed the compiler needs to be more or less clever.) An optimizing compiler lets you write simpler code but still have speed advantages. With type inference, you can catch type errors at compile-time but you don't have to write them out. Sure, OCaml's type checker is black magic code that's even doubly-exponential (that's O(2^(n^n))) in some cases, but once it's written, you don't have to worry about it any more.

1 Actually, it looks like it's an array of the subgroup matches? I never knew this, though, and I suspect most of you didn't, either...
2 Feel free to fix my Python if it's not idiomatic; I used to be pretty comfortable with Python but I'd never use it for string-parsing tasks.
3 All statements like these about natural languages really depend on how you count, but rough numbers will do.
  • evan

static typing / speed

I wanted to say static typing would speed things up, but this may show it's not so important. I wish it were about one tenth its size so I could print it...

Weirdly, I know Urs Hölzle from his talk here on Google's architecture. I guess Google just said "hey, it's clear you're really smart" and hired him to do whatever.

Also, I really need to start structuring my posts as theses+support, because that last one doesn't make any coherent sense now that I read back over it. :(