10 ms·
Dennis Ritchie on the priorities of && || vs. == etc. (1982)
- Sohcahtoa82 3y agoI don't even know the priority between && and ||. If I'm using both, I use parentheses just so it's more explicit. "a && b || c" should flag a linter, IMO, with "(a && b) || c" or "a && (b || c)" being required.
- JonChesterfield 3y agoConsider (&& a b) and (|| a b) for no uncertainty over operator precedence and easy variadic representation (&& a b c) at the cost of zero additional parentheses.
- namrog84 3y agoWhile true and might be easy to read in the example context of a b c I'd imagine having the && separator in real world conditions to be far more readable and clear for vast majority if people
- TylerE 3y agoPipe looks far too much like a 1 or lowercase ell.
- trealira 3y agoI actually think the variadic logical and is more readable, and wish more languages had it. As long as all the arguments are lined up vertically, you can think of it as a sequence of tests for true that exits at the first failure. It's more obvious with the variadic operator. Contrived comparison: if (a != null && x != 0 && a.use_it(x)) { ... } (if (and (not (null a)) (/= 0 x) (use-it a x)) ...)
- NegativeLatency 3y agoMost, and possibly all languages I've used day to day do not have prefix operators like that.
- ant6n 3y agoPrefix operators considered harmful for readability.
- tasty_freeze 3y ago... at the cost of having parentheses around every subexpression.
- torstenvl 3y agoI hate Excel for this very reason.
- o11c 3y agoConvert them to arithmetic. If you ignore the casting, a && b is just a * b a || b is just a + b Now you remember the precedence between them (except in broken languages, of which the only notable one is shell).
- dananabread 3y agoIs there an intuition for this correspondence otherwise I don't think it's very helpful
- dataflow 3y agoa * b * c is only nonzero if all of a, b, c are nonzero. That's AND, and should be pretty intuitive. a + b + c is nonzero if any of them are nonzero. (Remember each value is either 0 or 1.) So that's the intuition for OR.
- explaininjs 3y agoa+b+c is nonzero if any of a or b or c are nonzero.
- dataflow 3y agoYeah I just added that. I was hesitant initially cause you have to also note they're nonnegative, and that you're treating them like real numbers rather than mod 2.
- explaininjs 3y agoNatural numbers are the natural numbers for me! (ISO 80000-2, of course)
- naitgacem 3y agothis is the notation used in the chapters about boolean algebra in my digital design course. I think it's pretty neat. I honestly never looked into operator precedence in any language enough to notice the relation.
- userbinator 3y agoSorry, but I'm of the exact opposite opinion. If you don't know, learn. It's not hard (as other sibling comments have noted). Code with superfluous parentheses is even more confusing, since I expect them to be present only when overriding precedence. ...and I just realised your username adds some additional irony.
- sublinear 3y agoExplicit is better than implicit. I think the real problem is not breaking up complicated expressions. If it's more than just a few pairs of parentheses making it hard to read there's something more wrong there.
- thomasahle 3y agoI think most people agree "a*x**b + c" is more clear than "(a*(x**b))+c". Or "a+b+c+d" is more clear than "a+(b+(c+d))". Why are we happy to avoid parentheses for these operations but not for && and ||? Probably because we are all really used to the precedences for + and *. So at the end of the day, what's more clear depends on How familiar the engineers working on your code are with a given set of operators.
- djur 3y agoMost people might agree with that, I don't know, but I know I don't think your first example is clearer. And I've had the operator precedence rules memorized for decades. Parentheses save my brain a step. The second example is irrelevant because addition is commutative so the parentheses are meaningless. (This is why languages shouldn't override + to be a concatenation operator.)
- tsimionescu 3y agoThe important part that makes the parens in the second operation unnecessary is that addition is associative, not that it's commutative. And concatentation is also associative, so even if those were strings, the parens would still be unnecessary. That is, (a+b)+c == a+(b+c). Interestingly, C integer addition is not actually associative, since (1+INT_MAX) + (-1) is UB, but 1+(INT_MAX+(-1)) should in principle be defined as INT_MAX.
- MaxBarraclough 3y agoThe Ada language requires explicit parentheses in the case of a succession of different logical operators precisely to avoid precedence-related bugs. [0] In the Pony language, any expression where more than one infix operator is used must use parentheses to remove the ambiguity. [1] The Zig language considered following Pony, but didn't. [2] [0] http://archive.adaic.com/standards/83rat/html/ratl-03-06.html http://archive.adaic.com/standards/83rat/html/ratl-03-06.htm... [1] https://tutorial.ponylang.io/expressions/ops#precedence https://tutorial.ponylang.io/expressions/ops#precedence [2] https://github.com/ziglang/zig/issues/114 https://github.com/ziglang/zig/issues/114
- pi-e-sigma 3y agoAda is such an underrated language. The lack of its adoption is a testament to the old adage that technologies don't thrive or die on their technical merits.
- MaxBarraclough 3y agoThe late arrival of a Free and Open Source compiler certainly didn't help its adoption in Free and Open Source circles.
- wruza 3y agoI remember reading a discussion on it in a year around <= 2000 and it was a general consensus that Ada’s printed standard is too heavy to move around, let alone implement. Military/corporate-ish affinity didn’t help either.
- pi-e-sigma 3y agoCompare C++ and Ada. C++ standard is an order of magnitude bigger and more complex and yet C++ is thriving. So Ada's failure definitively is not due to the language complexity. C++ has been supported heavily by the corporate, too. Biggest C++ champion was Microsoft, after all.
- MaxBarraclough 3y ago
- JohnFen 3y agoI know the priorities of these, but I still use parentheses to make the precedence explicit. I think it's just great practice -- and it makes things easier for the next dev who might have to work with the code.
- User23 3y agoYou’re in good company[1]: Do not introduce priority rules that destroy symmetry. I remember how much more pleasant the predicate calculus became to work with after we had decided to give con- and disjunction the same binding power and thus to consider p ∧ q ∨ r an ill-formed formula. [1] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD13xx/EWD1300.html https://www.cs.utexas.edu/users/EWD/transcriptions/EWD13xx/E...
- thomasahle 3y agoThis article is a joy to read.
- Terr_ 3y ago> We don’t want to baffle or puzzle our readers, in particular it should be clear what has to be done to check our argument and it should be possible to do so without pencil and paper. This dictates small, explicit steps. I feel this justifies why I sometimes write code with lots of temporary variables on their own lines: It's a way to break down the problem, to name each thing (especially when it's not a simple method-call to a correctly-named method) and it also makes it easier to verify the behavior with the average line-by-line debugger or line-by-line debugging easier. In many cases, such temporary local variables have no negative performance impact, being optimized away.
- lifthrasiir 3y agoIt should be noted that this is not same to using a default order (typically, but not necessarily, left-to-right) in such case. Some languages use a term "non-associative" instead.
- moritzwarhier 3y agoIt bothered me a while for "short" boolean expressions to have a linter expect parens, where I'd simultaneously expect everyone with a high school degree to understand the implicit operator precedence. Then, since my first bug having been merged that was caused by a && vs || operator precedence mistake, I like my parens-always ESLint rule (or whatever exactly it was called). Even redundant parens can start to look as pleasent as indents, after getting used to them. In short, I think expressions like (a && b || c && d || e) are a footgun and linters should forbid them. Parens fit well with logical thinking, similar to relative clauses in language.
- asimeqi 3y agoI once implemented a logical expression evaluator to be used as filters on a search page. My boss tested it and filed a bug because he expected OR to have higher precedence than AND. I pointed him to a page with the rules and told him that he could use parenthesis if he wanted to modify the order of precedence. He was sort of convinced but never closed the ticket. Every release he would move it to the next release.
- paulddraper 3y ago|| is lower precedence than &&. I memorized that, just like I memorized that + is lower precedence than *. There are some languages (e.g. shell) where they have the same precedence. Prettier (JavaScript) adds parens like you suggest automatically.
- nealabq 3y agoSmalltalk doesn't have operator precedence rules. They just apply left-to-right. The infix operators aren't part of the language - they're messages (aka methods), and precedence would be inconsistent with the language's simple syntax.
- dehrmann 3y agoI'm always surprised when people don't know this one because they're like multiplication and clamped addition.
- Terr_ 3y agoAgreed, it's quite justified: If their implicit combined behavior isn't easy-to-read-obvious to the author composing the code, then it's also problematic for any poor future-person (perhaps even the original author) tasked with reading and verifying the code. Checking parens is faster than remembering and applying the knowledge. Plus the rules may be just different enough in different languages to trip up even those who try to remember them.
- zzo38computer 3y agoI also do (it also makes it more clearly, in my opinion), as well as with the bitwise operators & and | but sometimes with the bitwise operators the code is written in such a way that the precedence doesn't matter anyways and so I will not need to add parentheses anyways.
- torstenvl 3y agoDo you know the priority in this? [ -e "${target}" ] && echo "Getting information about ${target}" && stat "${target}" || echo "Failed to stat file." EDIT: What about the || die("...") pattern in Perl? Or the fact that the glyphs in || are visually lower than the glyphs in &&? Or that "and" comes before "or" in the expression "and/or"?
- pavlov 3y agoShell scripting sucks, but that’s hardly news. If I really have to touch a bash script, I just assume nothing works like I might expect from proper programming languages despite any surface similarity, and google every construct with sweat drops dripping down my face. ChatGPT has made the process somewhat more tolerable.
- PhilipRoman 3y agoThe shell command is very readable. a && b && c is a common idiom for "stop if any step fails". In the shell language && and || are more like control flow concepts, rather than binary operators. And a && b || c is a universal "ternary operator" (with one minor deviation) idiom across many languages, not just the shell
- cm2187 3y agoI just asked myself the question for SQL's AND and OR this week. Apparently AND takes precedence but I don't know if it is consistent accross all SQL engines. In any case it seems to me that few programers will know anyway, so it is bad practice to not have parenthesis.
- buserror 3y agoI do the same, not because I don't know the priorities, but because in my 35+ years career, I've seen so many times when cut+paste of partial expression leads to disaster just because it wasn't grouped properly with parenthesis.. Early in my career I erased several megabytes of shared memory on a mini computer, ie DOZENS of users worth of memory just because I was being 'clever' and cut+pasted bits of an expression the wrong way. Since then, as a rule, sod the priorities, parenthesis it is...
- dox_shepherd 3y agoThis is the best way in my opinion. Sacrifice a second of typing for clarity. I try to do this with expressions in general.
- o11c 3y agoSomewhere, I sat down and wrote a combined precedence table across many languages. It was quite hairy; sometimes there's even variation across language versions. Besides `not` (which often has different precedence depending on whether it's a keyword or a punctuator, and is a great reminder that multiple levels of pratt parsing is meaningful), `await` is the one with the most variation - in most languages, it binds tighter than binary operators, whereas in C++ it's just barely tighter than assignment.
- Denvercoder9 3y ago> `await` is the one with the most variation [..] in C++ it's just barely tighter than assignment. That doesn't seem right to me. According to cppreference[1] it's the operator with the third-highest precedence, well above assignment, which is the second-lowest. [1] https://en.cppreference.com/w/cpp/language/operator_precedence https://en.cppreference.com/w/cpp/language/operator_preceden...
- o11c 3y agoHm, I looked at Wikipedia, and I'm pretty sure cppreference used to agree with it? Did it ever differ between drafts? I've gotten surprisingly out of touch with the C++ world, even though it was my first serious language and I used to have the draft numbers memorized ...
- naitgacem 3y agoquite interesting. in VHDL we have a similar thing. `<=` is the assignment operator, however after an if, it means "less than or equal". I'll have to check it this was inherited from Ada. Edit: on a quick glance, Ada uses the := as the assignment operator. However there seems to be only one version of the and, or, xor operators, used for both logical and bitwise.
- not2b 3y agoThe relative priorities of && vs ||, or & vs |, match the traditional precedence in logical expressions: "and" binds more tightly than "or", just as * binds more tightly than + ("and" is equivalent to * for one-bit values, and "or" is addition modulo 2). So I think that they got this correct. However, the precedence of & vs &&, or & vs ||, etc is a source of problems.
- Denvercoder9 3y ago> However, the precedence of & vs &&, or & vs ||, etc is a source of problems. Do you have any examples of this? In my experience you almost always want the bitwise operators to have a higher precedence than the logical operators, as using the result of logical operators as a bitmask makes little sense. Consider e.g. `A & B && C & D`, which currently is equivalent to `(A & B) && (C & D)`, but with reversed precedence would be equivalent to the almost nonsensical `A & (B && C) & D`. Now, the precedence of == with respect to & and | is actually problematic (as Ritchie admits as well). Having `A == B | C` interpreted as `(A == B) | C` is almost never desirable. For extra annoyance, the shift operators do have higher precedence than comparison, so `A == B << C` does what you usually want (`A == (B << C)`).
- not2b 3y agoIt usually comes up if the user intended to write && and writes & at one point in an expression, the different precedence can produce an unexpected result. But gcc and clang have warnings for that.
- Denvercoder9 3y agoThe only operators with precedence between & and && are ^ and |, though. In e.g. the expression from the sibling comment, `a & b == c`, writing & or && doesn't make any difference (aside from short-circuiting). I guess it's an issue if you write & instead of && in an expression that also involves a bitwise-OR (or | instead of || in an expression that also involves logical-AND), but that seems quite rare. I expect that reversing the precedence of the bitwise and logical operators would create problems a lot more often.
- IshKebab 3y agoThere's a niche language called Sail that has & and | act as both bitwise and boolean operators. It actually works really well and isn't confusing at all after the initial "this is different". There's no real downside because it is a modern strictly typed language unlike C. On the other hand I don't know if there's a real upside either. It's not very difficult to use && and || and it serves as extra documentation, and everyone is used to it by now.
- secondcoming 3y agoWell in C-derived languages && and || will short-circuit the evaluation whereas & and | will not.
- shagie 3y agoAside, In Oral Tradition in Software Engineering by Bryan Cantrill, he talks a bit about the lack of logical xor in C. https://youtu.be/4PaWFYm0kEw?t=2236&si=MZPbopZPQtnnT-Tb https://youtu.be/4PaWFYm0kEw?t=2236&si=MZPbopZPQtnnT-Tb
- 48864w6ui 3y agoForths often use -1 as TRUE so logical and bitwise operators coincide.
- lifthrasiir 3y agoSo did BASIC traditionally, with no short-circuiting behavior AFAIK. At least Visual Basic has `AndAlso` and `OrElse` variants that do short-circuit though.
- masklinn 3y agoMost langages with a passing link to C support & and | on booleans (even when they have a legitimate non-integer boolean type). However they are eager which makes them generally useless. ^ is a lot more valuable.
- cyco130 3y ago
- theLiminator 3y agothis is why lisp is beautiful
- artsi0m 3y agoIndeed. But lisp is much more than prefix notation. Different notations like revers polish notation also easier to parse. You can evaluate RPN only using stack. Also, recently I learned about thread-last macro in emacs lisp. Using it you can evaluate forms from left to right, and using it you can write less parenthesis.
- deleted 3y ago[deleted]
- hibbelig 3y agoSeveral comments about && and ||. But where does == go?
- laxd 3y agoLower than either, so you can do logical (or arithmetical) expressions on both sides of ==. Has nothing to do with nature, it's all about pragmatic decisions on how we want it.
- Denvercoder9 3y agoWrong. In C and C++, == has a higher precedence than && and ||, so `A && B == C && D` parses as `A && (B == C) && D`.
- elromulous 3y ago== is really just a logical xor anyway.
- adwn 3y agoYou mean "xnor", right? It's the != operator which corresponds to xor.
- elromulous 3y agoI did! Thanks.
- laxd 3y agoWrong? Did'nt ask how C does it and clearly you did not understand my simple comment. Wtf is going on with hn? It's turning into yet another site were kids think they seem smart by intentionally misinterpreting and bickering.
- pi-e-sigma 3y ago
- ynik 3y agoUnfortunately even though this was called out as a mistake in 1982, much more modern languages (e.g. C#) are still copying it. As painful as breaking changes might be, they beat the alternative of dealing with a bad design indefinitely. At least in more modern languages, the type checker usually catches the mistakes caused by this design mistake.
- kibwen 3y ago> much more modern languages (e.g. C#) are still copying it Fortunately Go, Rust, and Swift all chose to defy C and fix the precedence of &, which I expect sets enough of a precedence (heh) for every language for the rest of human history to get it right.
- wiseowise 3y agoPython also fixed it.
- zajio1am 3y agoFor languages that have strict distinction between booleans and integers (or other non-logical types) there is the natural priority of operations: 1) algebraic 2) relational 3) logical From this, bitwise & and | are algebraic, so they should have higher priority than relationals. One problem is !, which as a logical operator should have much lower priority. Another problem is ==, which could be interpreted as both relational (when used on integers) or logical when used on booleans).
- orthoxerox 3y ago> After all, we had several hundred kilobytes of source code, and maybe 3 installations.... I don't know if the following addendum is apocryphal or not, but I still like it. Follow-up question: several hundred kilobytes? Why didn't you just grep for all instances of "&"? Dennis Ritchie: because that happened in 1972, and Ken would write grep only in 1973.
- teo_zero 3y agoPlease note that, despite the misleading title, TFA is about the precedence of bitwise operators & and | with respect to the comparison operators (== and all the others). In particular it was asked to Ritchie why the former are weaker than the latter in C, a rule generally considered a mistake because it makes it impossible to write common expressions like if (addr & mask == 0) without adding extra parentheses: if ((addr & mask) == 0) TFA is not about the relative precedence between & and | (which is well understood, although disputed, and has mathematical basis), or between && and || (which mimics & and |). And TFA is not even about the precedence of the logical operators && and || vs. the comparison operators. I'm writing this because I've seen a lot of comments that, while interesting in themselves, completely misunderstood the premise.
- goto11 3y agoThank you for this additional context. So in short, bitwise operators have lower precedence than comparisons to allow you to write: if (a==b & c==d) ... but of course, this means you can't write bitwise checks like this: if (addr & mask == 0) ... The problem could theoretically have been solved when the shortcut operators were introduced, by increasing the precedence of & and | to be higher than comparisons, but have the shortcut operators be lower. So you would be able to write both: if (a==b && b==c) ... if (addr & mask == 0) ... But this was not done due to concerns of backward compatibility with existing code, since now every expression using the old pattern would subtly change semantics. E.g. the first example would now be parsed as: if ((a==(b & c))==d) ...
- MaxBarraclough 3y ago> you could write [...] but of course, this means you can't write I found this rather difficult to read. You could write those expressions. They're legal C code. Whether they will have the expected semantics will depend on, well, what you expected. The more general problem is code that relies too heavily on precedence rules in the first place. Precedence-related bugs and readability issues are easily avoided, just use parentheses. As I mentioned in another comment in this thread, some languages force the programmer to do this.
- tialaramex 3y agoOne of the most intriguing novel ideas in Carbon (One of the "C++ Replacement languages" announced in 2022) is the idea that operator precedence should not be a total order. Lots of prior languages have tried either: 1. No operator precedence, expressions must use parentheses so that it's very clear 2. No operator precedence, everything just happens left to right regardless But Carbon says what if we do have precedence, but only between operators which programmers expect to have precedence - whenever it's unclear what should happen the compiler instead rejects that, the same way many languages won't let you ask whether 5 == "Five" because you need to explain WTF you intended as most likely you've screwed up and didn't realise they're completely different types.
- Jenk 3y agoSmallTalk has the left-to-right for everything principle because everything is message passing and "operators" are just binary (as in two argument) messages which catches many out. 2 - 1 * 5 will return 5, while many would expect -3. You must apply parentheses if you want a precedence other than ltr.
- djmcnab 3y agoWGSL (the shading language for WebGPU) has something similar[1]. For example, `a + b << c` will fail to parse in a conforming implementation, as will `a << b << c` and `a && b || c`. Note however that `a && b && c` does parse. I find these rules to be well-thought through. [1]: https://www.w3.org/TR/WGSL/#operator-precedence-associativity https://www.w3.org/TR/WGSL/#operator-precedence-associativit...
- borrow 3y agoNovel among mainstream languages, but not unseen before: https://blog.adamant-lang.org/2019/operator-precedence/ https://blog.adamant-lang.org/2019/operator-precedence/
- parenthesis 3y agoman operator