Arc Lessons

We want Arc to be good for writing programs, and one way to ensure that is to start writing programs while the language is still malleable. In the process we've learned a few lessons.

Shorter names are a big win.

Shorter operator names sounds like a pretty superficial feature. Maybe so, but it has a noticeable effect on code. Shorter names mean you don't have to break lines so often, which in turn means that you can see more of your program at once. It feels significantly better to program in Arc.

The biggest win is in the names of operators that tend to be outermost in nested expressions, like do (progn), map (mapcar), and fn (lambda). Those are the ones that send your code shooting toward the right margin.

It's worth spending time to come up with good, short names for commonly used operators. I have a couple principles. One is that you only need to remember what a name means, not guess what it means on first sight. And the more often something is used, the less mnemonic it needs to be. Examples: +, -, *, /, which are truly arbitrary.

Replacing setf with = has turned out to make code significantly easier to read, not just because = is shorter, but also because the odd shape of = makes it clear that an assignment is no ordinary function call.

Using + to concatenate sequences is a lose.

This kind of overloading is just a pun. I found that it actually made programs harder to read, not easier, because I kept thinking I was looking at math code when I wasn't.

As several people have pointed out, concatenation isn't addition. It's not commutative, for example. Ok, you were right; we're tossing it.

In general, overloading is complicated question.

My guess is that it's no coincidence that people always use a few operators in examples of overloading, just as it's no coincidence they always use fibonacci in examples of the dangers of naive recursive algorithms. It may be there are only a few operators you tend to want to overload, and that that's because there is something special about them, not because overloading in general is the right idea.

I consider this not merely an open question, but one that probably hasn't even been properly formulated yet. My intuition is that we'd be missing the real question, whatever it is, if we considered it a matter of "supports overloading" being an item on a checklist.

Perhaps it should be subsumed in the more general question, how should you define a new type?

Implicit local variables conflict with macros.

In previous Lisps if you wanted to create a local variable you had to do it explicitly with a let. In Arc we were planning to let users declare local variables implicitly, just by assigning values to them. This turns out not to work, and the problem comes from an unforeseen quarter: macros.

If you're going to declare local variables implicitly, you have to decide what the scope of the new variable will be. We decided we would make new variables local to blocks: a local variable created implicitly by assignment would have a scope that extended to the nearest enclosing do (Arc's progn).

Technically it can be made to work. We wrote a hideously complicated interpreter that allowed local variables to be declared this way. The ugliness of this code worried me: ugly things are generally a bad idea. But we tried to keep this feature in the hope it would make programs shorter.

What convinced us to toss it was not a technical problem but a social one. In a language with implicit local variables and macros, you're always tripping over unexpected lexical contours. You don't want to create new lexical contours without announcing it. But a lot of macros that don't look like blocks in the call expand into blocks. So we provided a second block operator, called justdo, which was like do but didn't create a new lexical contour (i.e. it is Common Lisp progn), and this is what you were supposed to use in macroexpansions.

The trouble was, I kept forgetting and using do instead. And I was thereby writing utilities with the worst sort of bug: the kind that might not show up for years, and only then in someone else's code.

It seemed to us a bad idea to have a feature so fragile that its own implementors couldn't use it properly. So no more implicit local variables.

This problem is not limited to Lisp. Macros and implicit local variables just don't seem to work well together. Meaning that any language that already has implicit local variables will run into trouble if they try to add macros.

Assoc-lists turn out to be useful.

Arc has a kind of data structure called a db. We don't specify anything about its implementation; but you can think of it as a hash-table. We expected dbs to replace most assoc-lists. They do replace some, but assoc-lists turn out to have a property that is very useful in recursive programs: you can cons stuff onto them nondestructively. We end up using assoc-lists a lot.