I've earlier posted an approach to having more readable
s-expressions, namely, sweet-expressions.
I think their overall approach is sound, by combining
3 things: indenting as syntax, name-prefixing
(I used to call this name-ending but name-prefixing is
a better name), and infix.

But in 2006, I noted that I wanted precedence and was
trying to work out the details... and I've come to believe
that for a general-purpose notation like sweet-expressions,
they are actually better WITHOUT support for precedence!
Here's why...

I've been trying to figure out how to
"flesh out" sweet-expressions with operator precedence, and I just
kept hitting one annoying complication after another.
Precedence is nearly universal among programming languages;
they're very useful, and only a few infix-supporting languages
(such as Smalltalk) lack them.
"Everyone" knows that 2+3*4 is 14, not 20, because of years of training
in math classes that you multiply before you add.
They're also pretty easy to code (it's an old solved problem).
But I've discovered that
in the typical use cases of a Lisp-like language's expression reader,
supporting precedence (in the general case)
has some significant downsides that are irrelevant in other situations.
Which is interesting, given how widespread they are elsewhere, so
let's see why that is.

First, let's talk about a big advantage to not supporting
precedence in sweet-expressions:
It makes the creation of every new list obvious in the text.
That's very valuable in a list processing language; the key advantage of
list processing languages is that you can process programs like data, and
data like programs, in a very fluid way, so having clear markers of new lists
using parentheses and indentation is very valuable.

Now let me note the downsides to supporting precedence
in the specific cases of a Lisp-like language, which
leads me to believe that it's a bad idea for this particular use.
Basically, adding precedence rules to a general-purpose list expression
processor creates a slippery slope of complexity.
There are two basic approaches to defining precedence: dynamic and static.

* Dynamic:
You can trivially create commands that dynamically
create new infix operators, as well as
express their precedence and direction.
Coding this is not a problem, and in fact there are several implementations
of this in Lisp already out there.
But using this kind of dynamism for s-expressions themselves
gets hideously complicated in many uses of a Lisp-like language.
In a Lisp-like language,
objects often shift between being data and being code.
Often you're working at multiple levels and meta-levels of input -
and you may not want their precedences to be the same.
Yet trying to handle those different levels
turns out to require very complex management of the reader routine... you
have to keep passing to the various readers the various incompatible
precedence states (ugh).
Combining code from different sources can be a nightmare if
they have different precedence values, too.
In short, having precedences dynamically vary by meta-level
creates complexities you just don't want to deal with.

* Static:
All of the above suggests that dynamic control is painful; one
solution would be a
completely static, pre-canned set of precedence levels.
That would eliminate passing state, at least.
And indeed, this is what most programming languages do; you don't get
to reset the precedence of C operators
(a few languages, like Prolog, are an exception here).
Fine, but someone has to predefine that set.
That turns out to be really hard; the meanings of various infix operators
are <i>not</i> cast in stone, so it's difficult to preset the precedence
levels meaningfully (since you don't know what the operators mean).
That's particularly true if, like me, you're trying to define a single
expression syntax that would work (more or less) on arbitrary
Lisp-like languages.
The one obvious case is that * and / have higher precedence than binary
+ and - basically everywhere.
That particular rule <i>could</i> be built-in, but if that's <i>all</i> you
build in, there's little evidence that it's worth the trouble.
This kind of odd exception (there's no precedence, except for * and /
over + and -)
is unlikely to help development, and it's yet another rule to remember.
And if we're going to have such a limited support for precedence anyway,
why not completely forbid mixing the operators -
the advantage there is that it would make the mapping between
lists and their textual representation much clearer.

It's easier to add precedence later, if that turns out to be important after
more experimentation.
But after the experimentation I've done so far,
it appears that precedence simply isn't worth it in this case.
Precedence creates complexity in this case, and it
hides where the lists begin/end.
It's not hard to work without it; you can even argue that (2 + (5 * 6)) 
is actually clearer than (2 + 5 * 6).
Precedence is great in many circumstances - I'd hate to lose it
in other languages - but in this particular set of use cases,
it seems to be more of a hurt than a help.

Of course, you can write code in some Lisp dialect to implement a language that
includes precedence.
Many programs written in Lisp, including PVS and Maxima, do just that.
But when you're implementing another language, you know what the operators
are, and you're probably implementing other syntactic sugar too, so
adding precedence is a non-problem.
Also, if you're really happy with s-expressions as they are, and just
want precedence in a few places in your code, a simple macro to implement them
(such as <a href="http://folk.uio.no/jornv/infpre/infpre.html";>infpre</a>)
works very well.
But sweet-expressions are intended to be a universal representation
in Lisp-like languages,
just like S-expressions are, so their role is different.
In that different role, precedence causes problems that don't
show up in most other uses.
It think not supporting precedence turns
out to be much better for this different role.

--- David A. Wheeler 

Reply via email to