| |
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.
|
|