Precedence: ordering or grouping?

As I’ve mentioned before, I’m part of the technical group looking at updating the ECMA-334 C# standard to reflect the C# 5 Microsoft specification. I recently made a suggestion that I thought would be uncontroversial, but which caused some discussion – and prompted this “request for comment” post, effectively.

What does the standard say about precedence?

The current proposed standard includes the following text:

The order of evaluation of operators in an expression is determined by the precedence and associativity of the operators (§13.4.2).

Operands in an expression are evaluated from left to right.

…

When an expression contains multiple operators, the precedence of the operators controls the order in which the individual operators are evaluated. [Note: For example, the expression x + y * z is evaluated as x + (y * z) because the * operator has higher precedence than the binary + operator. end note]

I like the example in the note, but I’m not keen on the rest of the wording. It’s very easy to miss the difference between operands for any given expression always being evaluated left to right, and operators being evaluated in an order determined by precedence.

I’ve always thought about precedence in terms of grouping, not ordering – I think of one operator as “binding tighter” than another rather than as being “executed before” it. When I consider precedence, I mentally apply brackets to group operators and operands together explicitly. Eric Lippert has blogged along similar lines but I wouldn’t want to put words into his mouth by suggesting he agrees with me. Interestingly, he includes:

Order of evaluation rules describe the order in which each operand in an expression is evaluated.

That’s certainly true about the order of evaluation of operands in an expression, but as seen earlier precedence is also specified in terms of “order of evaluation”. To me, that’s what makes the standard confusing.

Importantly though, when I expressed this in a meeting, smarter people than me said that they exactly thought of precedence in terms of order of evaluation. Grouping was just another way of looking at it, but a sort of secondary approach.

What does “order of evaluation” even mean?

Let’s take a closer look at the wording of the standard. It’s fairly clear what “operands in an expression are evaluated from left to right” means (ignoring the possibility that the “left” operand actually occurs to the right of the “right” operand physically due to line breaks). The left operand is completely evaluated, from start to finish, before the right operand is evaluated. Great.

But what about the “order of evaluation of operators”? Here it’s trickier. Does “evaluating” a + b include first evaluating a and b? Should “order of evaluation” mean “order of starting to evaluate”? If so, the standard would actually be inaccurate. Let’s go back to the example of x + y * z. We can view that as a sequence of steps:

Evaluate x.

Evaluate y.

Evaluate z.

Multiply the results of steps 2 and 3.

Add the results of steps 1 and 4.

Note that the multiplication definitely occurs before the addition. So that looks right. But if I rewrite it just a little to give more context (sorry about the bullets; it’s the only way I could get the formatting right):

Evaluate x + y * z

1 Evaluate x

2 Evaluate y * z

2a Evaluate y

2b Evaluate z

2c Multiply the results of steps 2a and 2b; this is the result of step 2

3 Add the results of steps 1 and 2; this is the result of the expression

At that point, it’s clear that we’ve started evaluating the + operator before we’ve started evaluating the * operator.

Similarly, if we view it as a tree:

+
/ \
x *
/ \
y z

… we hit the + node before we hit the *node.

So from the “starting to evaluate” perspective, precedence appears to fall apart. The ordering only makes sense when you start talking about the operator performing its duty with the already-evaluated operands – which is tricky for ??, ?. and ?: which don’t always evaluate all their operands. I suspect that’s fixable though, with careful wording – and I’m influenced by the fact that the term “precedence” is naturally ordering-related (one thing preceding another). Maybe it’s as simple as talking about the order of completing evaluation of operands.

Non-conclusion: over to you

So, what should we do in the standard? Given the range of views on the technical group, I said I’d write this blog post and canvas opinion. Readers: how do you as readers think about precedence? How should the standard talk about precedence? Is any aspect of the existing wording (in C# 5 specification it’s section 7.3.1; in ECMA-334 4th edition it’s section 14.2.1) particularly helpful or confusing?

The technical group is full of very smart people – all of them smarter than me and with a deeper computer science (and/or C# compiler implementation) background. That makes me simultaneously nervous of proposing changes – but also confident in my role of “interested amateur” in that if I find something confusing, I suspect some other readers will too.

I’m in no way saying it’s wrong to think of precedence in terms of ordering – albeit with a more precise definition of ordering than we’ve got now – but I’m suggesting it’s not the most helpful way of expressing it for readers. Just to be entirely clear, I’m not suggesting any sort of semantic change – if we change the wording of the standard, it would be purely about clarification, with no behavioural change.

I had originally intended to make this blog post as “on the fence” as possible, but the more I’ve looked at it, the more I’ve reinforced my original position – I can only apologise for not being terribly even-handed. I’m very happy to be corrected though, and look forward to reading plenty of comments. Don’t be shy.

Post navigation

58 thoughts on “Precedence: ordering or grouping?”

In my opinion, the wording already present is the same as is used in traditional Computer Science academia and as a result reads naturally. Perhaps that is because I come from that background and am biased (I realize you also have a masters in the field, just trying to give some insight into my understanding). Although it may seem counter intuitive to read precedence as being evaluated in a non sequential order, I feel that many aspects of Computer Science are like this (counter intuitive and non sequential) and as a result the wording does not seem confusing to me.

I think of expression evaluation semantics as a process of substitution and simplification by term rewriting. Thinking about it this way gives rise to more opportunities for optimization and is less definitely specified for purposes of predictability of evaluation of subexpressions that have side effects, though, and concern about portability of side-effecting subexpressions is one of the reasons Java (to take a concrete example) leans a different way than C++ on this matter.

With my predictable evaluation hat on, I think of evaluation as a post-order left to right traversal of the syntax tree, where the syntax tree is constructed according to the precedence rules.

Thus, in your example of x + y * z, the evaluation order would be: x, y, z, the multiply, then the add.

Note that this often requires more scratch space than evaluating x last.

Precedence and associativity of operators are determined by the C# grammar (a standalone precedence table is informative, not normative). The grammar determines the shape of syntax trees built from expressions like a+b*c. Order of evaluation is consistent with depth-first post-order traversal of syntax trees (http://en.wikipedia.org/wiki/Tree_traversal#Post-order), except for some special operators like ?:, ??, && etc.

To be clear, that’s in terms of “order of evaluation of the last part of the operator”, right? Whereas elsewhere “evaluating an operator” includes evaluating the operands first.
Good point about the grammar though – I’ll check whether that’s obvious in the standard.

I’m not exactly sure what you mean with ‘the last part of the operator execution’, but I think you mean ‘calling the actual instruction/function that implements the operator, after all operands are available’. If you take ‘look up a variable’s value’ or ‘take this immediate value’ to be unary operators, then this is perfectly consistent with depth-first post-order traversal of the parse tree.

One way of possibly clarifying the wording in the standard would be to explictly state that each rule in the expression grammar denotes a particular side effect, and that all side effects are evaluated in the order in which the parses for said rules end in the input, with the additional rule that nested parses have to be evaluated first, i.e. leftmost-innermost.

Yes, that’s exactly what I mean by “last part of operator execution”. Maybe it’s a matter of distinguishing between “the expression x + y” and “the + operator within the expression x + y”.
FWIW, I’m not sure that talking about depth-first post-order traversal of the parse tree will make the spec any less cryptic to those who aren’t already immersed in CS. (Feasible, yes – and I know that a lot of the spec’s audience is very CS-aware… but it still feels like there should be a simpler way of explaining it.) Not sure about the side-effect version either.

I would tend to agree with you, precedence and association are about how to build the parse tree, not necessarily about the order of operation.

But I’m curious about the operands evaluated left-to-right bit. Does that mean that a conforming compiler cannot reorder operand evaluations for performance reasons? In the expression (funcA() + funcB()) it is illegal to evaluate funcB() before funcA()?

Indeed – in terms of the “end of operation” which you get from the depth-first post-order, considering “the operator” to be “the bit that does things with the operands” rather than “the expression including the operands”.
We certainly agree about what the tree looks like and how everything behaves. It’s just a matter of the simplest way (in terms of knowledge transfer) of expressing that in the standard.

Can we simply agree to do what you would do on paper? Scan the operators to determine precedence, locate their operands and evaluate them, reduce, repeat. I would do what vreshetnikov said above. I would not even LOOK at an operand until previously more important operations had been reduced to an operand (* does it’s thing all the way through before I start to look at the left side of a +)
My thoughts are that we are making too much of this. How can so much attention be called for? We are building something that takes too much thought power to deal with! The whole idea of precedence, as I was told in my question on SO about it, is to simplify things. This is exactly like big-endian vs little-endian: someone should have simply decided and that is that. It is the point of having intelligence: to decide and move on, not get stuck in hairballs.
No one seems to have brought up a crucial point, which is that the compiler evaluates the expression, and so does the CPU at run-time. The compiler builds the tree, and it should put in order the most important thing first, eg: * before +, and all evaluations of operators likewise, not strictly left to right, which makes no sense at all. If you are building expressions with side-effects, woe betide you. And, me when I try to figure out your spaghetti code.

“* does it’s thing all the way through before I start to look at the left side of a +)”

Well, that’s just wrong and is not at all what vreshetnikov said. The left side of a + must be evaluated first because a may have side effects. Evaluating * or b or c before evaluating a defies the standard as well as a natural (left-to-right) reading of the expression.

I find this whole discussion odd. I thought that everyone — especially smart people with deep computer science backgrounds — understood that precedence is all about parsing, not execution, and that it defines a mapping from a partially parenthesized expression to a fully parenthesized expression. And such people should be aware that, in a functional world, “order of evaluation” is irrelevant but precedence and parenthesization are not, so it’s quite confused and irrational to define precedence in terms of order of evaluation. First you parenthesize (group) according to precedence, then you can impose arbitrary rules like left-to-right ordering to make side effects deterministic.

Three thoughts, hopefully not entirely useless:
1) I think you can safely discard your notion of “starting” to evaluate an operator, can’t you? Because it involves doing nothing except making a mental note of “I will have to evaluate this shortly.” (And in RPN even the mental note is redundant).

2) Translating between infix notation and RPN also says to me that “binding tighter” and “executing first” are isomorphic: things which in infix are bound tighter are in RPN executed first and v.v.

3) I agree that the interplay of order of evaluation of operands vs operators is non-obvious. I have always imagined it as something like a two step process, with operators evaluated at compile time and operands at runtime:
(step 1, at compile time) Parse the expression into a tree, thereby ensuring the correct precedence/order of evaluation of operators.
(step 2, at runtime) Evaluate the tree from left to right recursing depth first, thereby ensuring left to right evaluation of operands.
Which I think matches the sentences you quote from the standard (disclaimer: ianalOrComputerScientist)
Finally, thanks for posting!

IMHO, the best way to explain the order of evaluation of the operators and operands is to show how an expression is converted from math notation to operator method calls. E.g., “a + b * c” is really “a.+( b.*( c ) )”, which means that the evaluation is “a b c * +”.

As an aside, in the future, I hope C# adds a way to mark “pure” functions so expressions could be automatically compiled into parallel sub-expressions, but for now, there is far too much potential for side effects that are expected to occur in the “left to right” order, even where the operators are commutative.

PS — I would love to see C# support RPN expressions, if only to make string concatenation less verbose (without resorting to interpolation like C# 6’s new feature).

The specification is in error. Grouping is primary. The two sides of the innermost group should “run” first (left side, then right side), before anything outside that group. Otherwise you have TWO rules: one for operands, and a different rule for operators. That way lies madness.

I do think the part of the spec you quoted is easy to misread. However, it is accurate and fits the generally terse explanations of such a spec.

Here is another way to think of all this (which Chris F Carroll comes close to in his comment). In the presence of operator overloading, evaluating operators themselves (not just operands) could have side effects. If we think of precedence and associativity as determining how the expression is re-written to method calls, then order of evaluation becomes very clear (assuming you know C# evaluates method arguments left to right). So “x + (y * z)” becomes “op_Addition(x, op_Multiplication(y, z))” and it is then clear the evaluation order is x, y, z, op_Multiplication, op_Addition. This also makes it very clear why operands must be evaluated left to right for consistency with the rest of C#.

Perhaps something along the lines of the above approach would be a fruitful way of expressing this?

If we consider that evaluation of operands could have side effects, then evaluation of operators could as well. Does the specification state the order that operators are evaluated with respect to operands?

In other words, if the expression is x * y + z, then is z evaluated first or is * evaluated first?

In other words, if the expression is x * y + z, then is z evaluated first or is * evaluated first?

(I screwed up an earlier version of the response to this.)

The * is evaluated before z – the whole of the LHS of the + operator is evaluated, i.e. x * y, then the RHS of the + operator is evaluated, i.e. z, and then the values of those two operands are summed.

I think using the word expression in the sentence “Operands in an expression are evaluated from left to right.” is unclear. While technically true, it doesn’t make explicit that the operands are only considered on a per-operator basis within an expression (e.g. with x * y + z, the operands are first x and y, then the result of x * y, which is an operand to +, and z), which is what I guess you mean by grouping. However, I also don’t know what a better word would be. Operator invocation?

Doesn’t the confusion lie in the fact that the word “evaluated” is used for both operators and operands? What if we just introduce another word for when operators are “evaluated”?… Like “Executed”, “Completed” or “Actioned” (possibly not actually a valid word, but you get the picture?)

So..
“operands in an expression are evaluated from left to right” – That’s fine and makes sense.
“The precedence of the operators controls the order in which the individual operators are executed” – Makes more sense to me. Or “Operators are executed in order of precedence”.

One guarantee that the current text does not in fact make, but could, is in the ordering between the evaluation of the operands and the operators themselves. As an example: the current text would allow two evaluation orderings for a/b + c*d, to wit:

a
b
c
d
/ ## ab
* ## cd
+

as well as

a
b
/
c
d
*
+

and I had actually gotten the first meaning from the text as it stands, with the immediate reaction of ‘huh, that’s an odd guarantee to make… oh, it doesn’t actually make that’. So explaining evaluation in terms of a depth-first post-order traversal of the syntax tree actually guarantees more than the text as it currently stands.

Note that evaluation of ?: is a weird beast that requires the depth-first post-order traversal with a cut. a == b ? c : d
would not behave as expected if evaluated as

No, I don’t think that’s the case. Evaluating the left operand of + involves evaluating a/b – so that should happen before evaluation of the right operand, by the standard’s description of ordering of operand evaluation. What have I missed?

I may be misreading Section 14.2 of the current standard (given that you’ve only provided an extract of the text I’ve reverted to the published standard). The example in section 14.2 (which you have not provided in the text above) reads [Example: In F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and, finally, method H is called with the new value of i. This is seperate from and unrelated to operator precedence].

Now, this is only informative, but it does suggest that the wording does not strictly convey the meaning. The problem is in what is to be considered an operand in an expression. Is that a simple name, or literal? A recursive occurrence of expression or one of its productions? In the first case, the evaluation of the i’s would would not necessarily result in a different value being passed to H. In the latter case, the exact order is only completely defined by looking at the point in the input where the recursive occurrence of a production of expression ends, as there may be multiple recursive occurrences of a production of expression starting at the same point in the input. The text itself does not make this choice of what is to be considered an operand in a compound expression explicit, thereby leading to a possible confusion; omitting the example in your blogpost served to trigger that confusion in my reading.

I’d say the operands of the + operator in that expression are F(i) and G(i++), which are sub-expressions. So they need to be evaluated first, which includes the side-effect of i++. Each time i occurs as a sub-expression, it is logically re-evaluated, even though the compiler and/or JIT may be able to optimize in some cases.

Sorry for omitting that particular example, but I didn’t want to end up quoting the whole section, which would have been rather long.

This I understand, but the text of the standard is not explicit about it (unless you read the informational, not definitional, bits). It is explicit about the left- to right logical evaluation of all operands, and about the precedence-following logical evaluation of all operators, but it is not explicit about what it considers to be operands. Maybe being more explicit as to what in the grammar is considered an operand for purposes of the ordering guarantee is enough…

My mental model is that precedence defines grouping. It feels like it’s in a different layer from order of evaluation. In my head the process goes: 1. apply precedence rules to slap brackets everywhere/build an expression tree; 2. evaluate the leftmost evaluable subexpression; 3. repeat 2 until done. It feels redundant to say that the precedence rules are involved in determining evaluation order – they did their job in determining the meaning of the expression in terms of which operations will feed input to which other operations, then they are done and the left-to-right rule gets to work ordering those operations. I don’t really think of “beginning to evaluate” an expression, though. (I’m not 100% certain my mental model is correct or that this is a complete description of it. Counter-examples welcome.)

Side-note: In primary school I had a reasonably intuitive understanding of parentheses in maths expressions that was rather shaken by the teacher saying that they changed the order of operations. I’m not even sure how I would have expressed the idea in words – to me it was obvious what they meant, but when the teacher said they “changed the order” and “moved things first” I was completely thrown. She made it sound like it did some weird shuffling of arguments. To my young self the word “order” implied the arrangement of symbols on the page, not the sequence of computations. It took me a while to understand that she was describing the same idea I had in the first place.

The teaching is best when the student says, “I thought it myself.” (To paraphrase the Chinese slogan) So, when you have through it through all ways, including the wrong ones, you really know your stuff. A good teacher should smile when seeing a student’s mistake, not be perplexed. Programmers as well, since they are “teaching” the computer.
Perhaps it is inevitable that in exploring something, we should reach the point where things like Grouping and Order of Evaluation can have subtly different meanings, but in everyday life, we want to stay far from there. “You are on a hiding to nothing”

I’ve always understood the “order of precedence” to do the following to “x + y * z”, firstly you would group your operators: “x + (y * z)”, but as x, y, z can be anything including other statements, you’re still “missing brackets” as you’d probably be told at school, so the actual statement is fully written as “((x) + ((y) * (z)))”. Looking at that though, not sure it helps you, as that would suggest y and z are evaluated first, as they are in effect a sub statement of the whole, which contradicts the L/R order of operands.

But I don’t think you’ve “started to evaluate” the + operator just because you’ve evaluated x, and I’m not sure many people would.

The “started to evaluate” is a matter of “x + y” is an expression consisting of the + operator with two operands. How do we evaluate that expression? First evaluate x, then evaluate y, then add the two. All of that is part of evaluating the expression, IMO, and I’d view that expression as “a binary + expression” in that if you drew a tree, the root node would be +. But yes, it’s entirely possible that others don’t think of it that way…

Nope, no contradiction – though you may have misunderstood. In any expression of the form x + y * z, the first sub-expression to be evaluated is x. That might just be a variable, or it may be a method or something else. As Weeble said though, it’s unclear what the purpose of your numerous comments really is. It feels like you’re trolling, to be honest – so if you’re not, it would help if you could clarify. (If you don’t, and if future comments still feel like trolling, I’ll just delete them.)

Do you see it? Addition is commutative, so “x + y * z” and “y * z + x” should in all ways be entirely equivalent. If x “does something” and it matters which side of y * z it is on, then we have struck an iceberg.
But even leaving that aside, if x “does something” and y * z is supposed to have precedence, then y must “do what it does” FIRST. Otherwise, you are evaluating the entire potential tree of x before you even get to y! Is this what precedence means?

Personally, even with my severely limited background in math and computer science, I am much more comfortable talking about the order of operations or operator precedence. Operator grouping is something that I had not really considered until I read your post.

Since you seem to be searching for something readily understandable by the widest audience a quick Google search across the following four phrases may provide some insight.

Please, please, let’s make it “as simple as possible, but no simpler.” We don’t need another big- little- endian concept. Why doesn’t the standard just say: “Do what you were taught to do in elementary school, using paper and pencil”? Computer math should work exactly like math in the real world. If there is a dispute in real-world math, for God’s sake settle that first.

Why doesn’t the standard just say: “Do what you were taught to do in elementary school, using paper and pencil”?

Because that’s extremely imprecise – aside from anything else, different people have been taught different things. And you’ve already demonstrated in other comments that your expectations are at odds with those of other people.

Personally I’m very happy with the behaviour the way it is, and just want to convey that as simply as possible.

Of course, there are plenty of languages which don’t guarantee evaluation order, but I’m glad C# isn’t one of them.

If my expressions were so complex that different people could understand them differently, I would simplify them. Code is for people, not computers, the computer doesn’t need it.
How can math be taught differently? To me, math is absolutely the one thing that we absolutely know for sure. It is not a matter of belief, but definition, and definitions cannot be at odds, or someone is simply in error. If we don’t at least agree on that premise, then the whole enterprise of programming is a bust. Like the big- little- endian thing: a matter of opinion. Programmers are not paid to have opinions, they are there to get a job done, like Engineers. At the end of the day, one thing works and another thing fails. Life teaches us what works. It is not an art-form.

This does not seem to address either the parent comment or the post itself. It’s not that it’s off-topic, more that it starts in the same general topic and then travels in a direction orthogonal to the rest of the conversation. Is that your intention? I’m not trying to be mean, but I’m not sure how to interpret your comment. It could be #1: an attempt to argue your previous case that the standard should say something like “Do what you were taught to do in elementary school, using paper and pencil”, although if it is, I disagree and don’t understand how this piece supports the argument. It could be #2: an argument that the language should change in some way to prevent complex expressions. Or it could be #3: an unrelated train of thought, in which case, okay, I have no comment other than to say you could be a bit clearer about what you’re communicating and why.

Well, I stated that it should work like math does, and was told that people differ on math. If that is true, then I cannot see how computer science can have any basis, or hope to accomplish anything. When a question arises, you have to appeal to a higher level of understanding, and in the end, make a decision (ie: axioms). It seems that the standards people have either failed to make a decision, or made one that is at odds with math or common sense. If I found myself in that situation, I would stay as far from that area of the standard as possible – keep things too simple for the issue to arise in my code. Life is too short to waste any time on matters of opinion where it does no good. (Does opinion ever do any good?)
This question should have been settled so long ago and so decisively that it can no longer arise. Why even take a moment to consider such a fundamental idea? We could have cured cancer by now with all this wasted brainpower!

What a great point of view. I think in matters of calling it science, its really confusing that the precedence of operators is put over the priority mathematically speaking. Why should we ignore the mathematic behaviour, in means of why the compute, i think this might be easy for some cases like ? and ?? that its order its explicit by itself. But as I studied algebra, it would never get in my mind that 2 + 5 * 2 = 14 and not 12. Why would a language impose an algebraic misinterpretation? I know that for bool operators is meaningful, but not to algebraic (non-boolean) expressions.

“The precedence and associativity of C operators affect the grouping and evaluation of operands in expressions. An operator’s precedence is meaningful only if other operators with higher or lower precedence are present. Expressions with higher-precedence operators are evaluated first. Precedence can also be described by the word “binding.” Operators with a higher precedence are said to have tighter binding.”

This wording is a good step in the right direction because it concisely differentiates operators and operands – operators dictate precedence and operands are the result of an expression. That being said, this still feels oddly verbose to me for such a plain concept, especially given how easy it is to draw parallels between real life arithmetic done with pencil and paper in terms of grokkability. I would venture to say something along the lines of the following:

“Expressions containing operators determine the order of operand (sub-expression) evaluation by the precedence level of those operators. Operators bind operands together using an established order of precedence so that evaluation is consistent for every expression, evaluating each operand of similar precedence level from left to right when it is encountered.
As an example, the expression W + X * Y / Z contains three operators of two different precedence levels, requiring an order of evaluation between the multiplicative and additive operators. Since the multiplicative operators have a higher precedence than the additive operators, the evaluation of the expression is broken into the operand X added to the sub-expression W * Y / Z. Since this sub-expression is entirely the same level of precedence, its result can be considered a single operand when encountered, returning its result as the value for the parent expression.”

I’m sure that could be worded better, but the general point sticks – you don’t calculate the higher precedence operations first, you calculate the entire sequence of them until you reach another operator of a different precedence.

Ah… yes, “operation” makes more sense to me in context, I can’t quite grasp how or why you’d “evaluate” a + operator… the + operation sure, the operands of the operator sure, but the operator??

If you had (a + b * c), it seems like the operator * should determine which operand/operation is evaluated first due to the precedence of the operator.

If I had a(b * c) assuming a is a function, I can’t actually evaluate a before I’ve evaluated b * c can I? If a is an operand (and it would be wouldn’t it?), doesn’t that invalidate the notion that operands are evaluated left to right?

Precedence is both grouping and evaluation order, meaning that you were taught to group x + y * c to x + (y * c), and then calculate from the innermost parenthesis. Postfix and prefix languages don’t have any precedence, because the operands are grouped automatically by their position.

“smarter people than me said that they exactly thought of precedence in terms of order of evaluation. Grouping was just another way of looking at it, but a sort of secondary approach.”

They may be smart, but they aren’t applying it here … in fact, they seem to know little about the subject of programming … Precedence is about parsing, i.e., “grouping”. Precedence can be defined in terms of a transformation of the source into a simpler language without precedence by adding parentheses. Once that’s done, you can hobble the compiler by imposing an arbitrary rule like left-to-right evaluation, but it has nothing at all to do with precedence.