upvote
I think I’d generalize that rule to require parentheses in any situation where adding parentheses could change the interpretation. I think that’d leave int addition and multiplication, and I don’t think there’s anything else offhand. Other than those, require parentheses.

  a - b - c
is order dependent, even if its deterministic and knowable. When I’m scanning the code to look for a pesky bug, I don’t wanna have to take extra seconds to convince myself that it’s doing what I expect. It steals time and my limited attention from more interesting sections of code.
reply
> I think that’d leave int addition and multiplication, and I don’t think there’s anything else offhand. Other than those, require parentheses.

At this point you just require every compound infix expression to be parenthesised, the terseness isn't worth the inconsistency. Especially as, as others have noted, these operations are only associative when working in some classes (notably not necessarily when dealing with floats).

And then you do automatic parens insertion in the LSP, so you write

    a - b - c
and when you save the lsp fixed it up to

    (a - b) - c
reply
- and + operators have the same precedence. And a similar bug is possible if the operators were the same (both -). So I’m not sure it’s right to blame this on operator precedence or mixed operators. It’s just that, ultimately, the “consume” needs to be subtracted, not added.
reply
Non-mixed always goes strictly left to right, regardless of the operator, which I haven't seen anywhere near as much struggling with.

But yes, I personally parenthesize `a-b-c` explicitly, because it's not worth it for me to read and wonder if parenthesizing order matters later. Costs less than a second to write, saves a second or ten each time I read it - that's an excellent tradeoff imo, and is a trivial pattern to follow.

(Associative operators are fine, obviously)

reply
I agree with explicit parentheses but please be careful about assuming associativity! The risk when handling floating-point arithmetic in particular is that associativity breaks, and suddenly a + (b + c) does NOT equal (a + b) + c. Not only can these lead to unexpected and hard-to-trace failure patterns, but depending on the details, they also can introduce memory overflow/underflow vulnerabilities.
reply
Didn't you just suffer from the same trap the parent was trying to avoid?
reply
I once had a job interview where they wanted to evaluate my C knowledge. They showed me a printout of some pointer arithmetic and said spot the bug. (It may actually have been the old puzzle where it turns out that /* is always a comment opener and never a division by the referent of a pointer).

I said "well first, this is a mess, I'm putting parentheses here, here, here and here". They said "well you've fixed the bug but can you tell us where it was?"

I gave them a hypothesis but I said my "real answer" was that it's not worth our brain cycles to figure it out, you just shouldn't write code that requires knowing operator precedence. It's just such desperately boring information that I can't hold it in my head.

Interviewing such an insufferable smartarse was probably quite annoying but they did give me the job and I do stand by the underlying principle!

reply
Smalltalk didn't have math operator precedence, and I thought it was very annoying but I've come to believe it was a good idea.
reply
That's what pony did also. Operator preceding rules are too arcane, such as the need for manual memory management.
reply
Nice: https://tutorial.ponylang.io/expressions/ops#precedence

Yeah that's pretty much exactly what I do by hand. I should really give Pony a try some time... there's a lot of stuff in it that I like.

reply
IIRC several industry and government coding standards don't permit evaluations in arguments to functions, as the compiler can end up doing wonky things, to say nothing of the likely human error. These are the kind of standards we should be adapting into a software building code to avoid security holes like this one.
reply
These standards are that way because older languages (specifically C and C++) have unspecified evaluation orders for arguments, so multiple argument expressions with conflicting side-effects are non-portable.

Here the expressions are pure, OoE has nothing whatsoever to do with the issue.

reply