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
