[HN Gopher] Better Operator Precedence
___________________________________________________________________
Better Operator Precedence
Author : kelseyfrog
Score : 43 points
Date : 2021-10-29 21:50 UTC (2 days ago)
(HTM) web link (scattered-thoughts.net)
(TXT) w3m dump (scattered-thoughts.net)
| tantalor wrote:
| > There is a technique that solves both problems!
|
| What's the problem? What is the point of this article?
| auggierose wrote:
| Here is a way of defining precedence that is quite general:
| https://obua.com/publications/practical-types/1/ (Search for
| APP_PRIO in the document to get to where this is talked about)
|
| Basically, an expression "A + B" is given a certain precedence,
| and both A and B are expected to have higher precedence. You can
| modify that by putting a ` in front of any nonterminal, for
| example "`A + B", which means that A can have indeed the same
| precedence as "A + B". This way you can specify associativity,
| and this works also in other cases, for example "[?] x. `P" means
| that something like "[?] x. [?] y. x = y" parses correctly
| instead of having to write "[?] x. ([?] y. x = y)"
| moeris wrote:
| J has no operator precedence, and evaluates from right to left.
| Pretty easy to get used to.
| civilized wrote:
| It's interesting what people find easy and hard.
|
| I find infix operators and their standard mathematical
| precedence rules perfectly intuitive. Equally good are the
| extended precedence rules of languages like R, which were
| designed by professional users of mathematics.
|
| I hate reading S-expressions and reverse polish notation. To
| me, proposals that we write in those notations are like saying
| that we should write assembly code. I think "No, we have
| compilers so that humans can write human-readable code rather
| than being forced to cater to the machine."
| SomeHacker44 wrote:
| Came here to say the same about APL, of which J is a
| descendent.
| rak1507 wrote:
| Came here to say something similar. This is the best option,
| and I would like this in every other language. Totally avoids
| any ambiguity at all.
| patrec wrote:
| This is just untrue. You can't even parse J without knowing
| if something is an "adverb" or a "verb".
| rak1507 wrote:
| That has nothing to do with RTL 'operator' precedence. Ok,
| sure, there's some other precedence stuff in J, but the
| actual thing that was the topic of this post is not related
| to it.
| jancsika wrote:
| What bad things happen if you just evaluate everything left to
| right and use parentheses to force evaluation?
|
| I.e., with no parenthesis it works exactly as a layperson typing
| numbers into a calculator and feeding the answer into the next
| calculation. And with parentheses, the layperson can specify some
| calcs to happen out of order.
| colejohnson66 wrote:
| It breaks the expectation of programmers that operators in
| PEMDAS (or whatever you call it) are evaluated how you expect.
| If I type: d = a + b * c
|
| I expect it to be evaluated as: d = a + (b *
| c)
|
| Maybe that's just me, but I bet I'm not the only one that
| thinks that way.
|
| For non math operators, you may have a point...
| userbinator wrote:
| _because the precedence rules of arithmetic are familiar to most
| people._
|
| _It 's not obvious to most people what precedence to expect_
|
| Nothing is obvious to anyone who hasn't learned it yet. Where do
| you draw the line? The rules of a natural language are orders of
| magnitude more complex, yet humans have no problem learning and
| using them. There are _twenty-six_ letters in the English
| alphabet, and "most people" wouldn't have trouble answering if C
| comes before or after J. Yet you look at the few more levels of
| precedence in this other language you use nearly every day, and
| "OMG too hard!!!!11" ?
|
| It's sad to see this strange sentiment of "anti-intellectualism"
| or "anti-learning" that seems to be slowly growing in the
| programming community; and turning a linear one-dimensional
| ranking into a two-dimensional sparse matrix of comparisons seems
| like the exact opposite of a solution or reduction in complexity.
| xiaq wrote:
| Not everything is worth learning for everyone, especially when
| it's just something arbitrary people need to remember.
|
| If I invent a crazy unit system for measuring distances, where
| a "finger" is 8.3 centimeters and a "storey" is 246 centimetres
| and start using it in my writing, can I really blame others for
| "anti-intellectualism" when they refuse to learn my arbitrary
| new unit system?
|
| The problem with operator precedence is just that - any
| operator precedence rule is an arbitrary convention. I'm not
| saying that conventions are never worth learning, but it
| depends on (1) whether such convention is already well
| understood, and (2) whether the benefit of such conventions
| outweigh the trouble of requiring everyone to learn it. The
| precedence between * and + clears both criteria: most people
| have learned during school, and it's quite common to use both *
| and + in the same expression that defining that * binds tighter
| than + can save a lot of parentheses. On the other hand,
| combination of + and | are rare enough that the benefit of such
| conventions doesn't outweigh the confusion it could cause.
| daniellehmann wrote:
| My reading of the article is not that it promotes "anti-
| intellectualism" or that it argues to remove all implicit
| precedence or associativity rules.
|
| I think it actually gives a nuanced answer to your question
| "where do you draw the line", namely: With a partial precedence
| scheme, one does not need explicit parentheses for those
| operator combinations for which the precedence is "clear
| enough". E.g., in "3 + 2 * 4" precedence is clear to virtually
| all programmers because it follows standard rules from math
| ("PEMDAS"), so that is why the precedence table in the post
| specifies an ordering. However, especially for operators which
| are less frequently used, or where precedence does differ
| between languages, one should give parentheses for clarity. I
| think this is a very sensible argument.
|
| I have certainly made errors because of unclear precedence
| (e.g., with boolean operators, exponentiation, casting etc.) in
| the past. And given that there even is a CWE number
| (https://cwe.mitre.org/data/definitions/783.html) for this kind
| of error, it seems frequent enough to warrant discussion.
| ModernMech wrote:
| > There are twenty-six letters in the English alphabet, and
| "most people" wouldn't have trouble answering if C comes before
| or after J.
|
| Maybe we need a song to teach operator precedence. The PEMDAS
| acronym doesn't have the same lasting impact as the alphabet
| song it seems. Does Sesame Street feature any segments on
| arithmetic? I don't remember any from when I was a kid. Are
| kids who watch Sesame Street too young for that?
| naniwaduni wrote:
| > There are twenty-six letters in the English alphabet, and
| "most people" wouldn't have trouble answering if C comes before
| or after J.
|
| To be fair, this has a lot to do with the fact that C is pretty
| early in the alphabet, where it's easy to enumerate until you
| hit C before J. A _lot_ of people couldn 't say off the top of
| their head whether S comes before or after O, or whether R
| comes before or after V.
| [deleted]
| snicker7 wrote:
| The Lisp and APL families of languages do not have operator
| precedence at all.
| mjevans wrote:
| If you aren't sure, a future maintainer might also have the same
| problem.
|
| Write it out clearly and use extra () clarity to be completely
| obvious what is meant.
| Taniwha wrote:
| Solved this problem years ago for Algol68 (which allows you to
| specify relative precedence) by parsing with an LALR generated
| parser that left the resulting shift/reduce conflict to be
| resolved at run time (by looking at the operator on the stack and
| the one just scanned and comparing their current precedences)
| lpapez wrote:
| My programming teacher told me: "operator precedence is
| complicated, always use parenthesis if you have multiple
| operators in a single expression". I simply do that, and nobody
| complained so far :)
| taneq wrote:
| I do the same. I'm pretty confident with my BIMDAS, less so
| with which operators are left- vs. right-associative in what
| language (and I hop around languages a lot so this is a bit of
| a pain). My general rule is if I have to look it up, then
| chances are so will the next guy (who in my line of work may
| not even be a coder at all and will be properly flummoxed), and
| chucking in brackets is a double win because it saves us both
| time.
| samwillis wrote:
| I do the same as well as break up the expression with
| intermediate variables to help explain to someone else (or
| myself three years down the line) what I was thinking. Any
| compiler/interpreter (for a language you'r using where speed is
| important) is going to flatten it anyway and so long
| expressions aren't going to make your code faster!
|
| Always code for someone to read your code later.
| agumonkey wrote:
| You have nice colleagues.
| OskarS wrote:
| For operators where it's not obvious, sure, but are you really
| writing if (((2*x) + 3) == z)
|
| instead of if (2*x + 3 == z)
|
| If so, that's pretty annoying.
| [deleted]
| jraph wrote:
| I would not personally go that far. I may avoid parentheses
| for whatever is already commonly not put between parentheses
| in the code base I work on and would default to parentheses
| otherwise.
|
| But another tool to work around this issue is to just give
| names to sub expressions by storing them into constants. Long
| expressions quickly get unreadable anyway.
| ModernMech wrote:
| Writing it is annoying, yeah, but it is more comprehensible.
| Since code is read more than it's written, the annoyance of
| writing it is a fine price to pay. Also more annoying than
| writing it is having to debug subtle bugs caused by incorrect
| understanding of precedence on the part of the programmer.
| OskarS wrote:
| My point was that the second version is much easier to
| read, it's a lot clearer.
| tromp wrote:
| Reading "if (((2*x) + 3) == z)" is annoying as well to
| me...
| ModernMech wrote:
| But it's _clear_ and _precise_ , which is the definition
| of good code to me, and good developer experience. Clear,
| consistent rules applied uniformly, everywhere. That's
| what I call good design. Bad developer experience is
| having to remember 17 levels of precedence, as in C++[1].
| It leads to ridiculous things like (*foo).bar, which
| requires new syntax to make readable, and that's just one
| more thing to remember and teach. Do you know how much
| that confuses students new to C and C++?
|
| [1] https://en.cppreference.com/w/cpp/language/operator_p
| receden...
|
| But if it's really so onerous to read, you can always put
| a more ambiguous version in the comments!
| eperdew wrote:
| To me, adding parentheses when using common operators
| signals that I should read it carefully, because normal
| precedence is probably broken. When that is not the case,
| I spend a bit more time parsing the expression for no
| gain as a reader.
| codeflo wrote:
| I remember reading a Perl 6 design document back in the day that
| made a convincing argument that operator precedence should be a
| partial instead of a total order. The language was supposed to
| have user defined operators. The idea was that you would make
| some declarations that your new operator would bind e.g. "between
| * and +", or "tighter than *". The system would build up a
| minimal partial order that satisfied these constraints.
|
| One of nice things about such a system is its compositionality:
| If you mix and match custom operators from different modules, you
| wouldn't get a conflict or a random precedence order, instead,
| you'd simply be forced to disambiguate using parentheses.
|
| Contrast this with Haskell, which also has custom operators, but
| only a fixed number of precedence slots (10 IIRC), and some
| libraries really suffer from the fact that there's no "good one"
| available that works for the intended usage.
| agumonkey wrote:
| I'm stumped by the fact that haskell relies on a fixed slot
| count.
| patrec wrote:
| Yup, Fortress had the same idea, I wonder if independently.
| zokier wrote:
| Documentation of Raku (nee Perl 6) custom operator precedence
| mechanism https://docs.raku.org/language/functions#Precedence
| mayoff wrote:
| Swift works the way this article describes, except that instead
| of the rules being built in to the compiler, they are defined by
| the standard library, and you can define your own precedence
| levels and associativity for custom operators.
|
| https://github.com/apple/swift/blob/3ea9e9e55281b9957d2b5486...
| [deleted]
| daniellehmann wrote:
| I don't know Swift, but does this mean there are expressions in
| Swift where the compiler returns an error, because the
| expression is ambiguous without additional parentheses?
|
| (My understanding of the article is that it mainly argues for
| _partial_ precedence, not so much that precedence/associativity
| can be defined in the program - the latter is also the case in
| other languages, e.g., Haskell.)
| superlopuh wrote:
| Yes: precedencegroup MyPrecedence {}
| infix operator +++: MyPrecedence func +++ (lhs: Int,
| rhs: Int) -> Int { lhs + rhs } let a = 1 + 2 +++ 3
|
| Error: Adjacent operators are in unordered
| precedence groups 'AdditionPrecedence' and 'MyPrecedence'
| daniellehmann wrote:
| Very cool, thanks a lot for the example!
| amelius wrote:
| You could also build a matrix of precedences.
|
| Also, they should mention ternary operators.
| Waterluvian wrote:
| I'm fine with the popular appproach but what about a very simple
| left to right precedence?
|
| I'd love to imagine a little number monster pacmanning his way
| across the line, munching numbers and operators that change his
| colour and effect.
| Qem wrote:
| Pharo actually does that. it inherited from Smalltalk, and I
| think also APL before.
| igouy wrote:
| Some parts of the Pharo website actually dare to say "Pharo
| Smalltalk".
|
| https://pharo.org/documentation
|
| site:pharo.org smalltalk
| musicale wrote:
| Hopefully that will spread to the rest of the site, since
| Pharo Smalltalk is a clear and accurate description and
| it's confusing/misleading to imply that Pharo isn't a
| version of Smalltalk.
| igouy wrote:
| And in this case the behavior discussed is basic
| Smalltalk-80 not some innovation.
| roywiggins wrote:
| MUMPS does this, but unfortunately MUMPS is a horroshow of a
| language.
| gumby wrote:
| I've never liked operator precidence, perhaps because my first
| language was lisp, but even now 40+ years later I still
| parenthesise everything just in case.
| MaxBarraclough wrote:
| Agree that that's the more readable, less error-prone to write
| code with infix notation (provided it's done in moderation of
| course).
|
| If I have to stop and think about operator precedence, that's a
| bad sign.
| drmajormccheese wrote:
| Fortress didn't have a total order of operators. Also IIRC
| operators could be tightly bound or loosely bound based on
| whitespace.
| shaunxcode wrote:
| Or just use sexpressions. I don't mean that flippantly either. I
| truly believe all of mathematics should utilize them as well.
| This way you learn the basics in grade school and have the
| syntactic skills needed moving forward.
| zarzavat wrote:
| Sexprs really suck to write by hand without editor support and
| a lot of mathematics is done by hand.
|
| RPN might be a better starting point for a revolution.
| seanmcdirmid wrote:
| They also suck to read. Operator precedence is complicated,
| but actually increases readability once you are used to them,
| so much so that the parens seem to get in the way. Which is
| weird, why could that be?
|
| Perhaps the extra tree parsing required by the brain is more
| than made up for by some kind of visual compression economy
| going through the eyes?
| agumonkey wrote:
| what about RPL then :)
|
| sexprs without pair matching are horrid, but it's a solved
| problem since a few decades (paredit was made by zeus)
| dhosek wrote:
| I remember being confronted with an HP calculator for the
| first time in college. I understood RPN just fine and knew
| that I wanted to do, e.g., 6 7 x, but turning that into
| actual keystrokes on the calculator was not immediately
| obvious. I think I ended up doing something like 6 [?] 7 [?]
| x [?] instead of 6 [?] 7 x and got the understandable garbage
| result. I ended up doing a big chunk of my freshman physics
| homework doing the math by hand because I didn't own a
| suitable calculator.
| gumby wrote:
| The rpn calculator does the calculation the same way you
| would by hand -- that's the point. I'm sorry you had a
| difficult experience.
| codr7 wrote:
| The only reason for mandatory parens is varargs.
|
| https://github.com/codr7/ampl
| naniwaduni wrote:
| To a human reader, every function is vararg until proven
| otherwise.
| MrManatee wrote:
| How about relation chaining, such as with inequalities? Is
| there some nice S-expression notation for, say, "0 < x <= y <
| 1"? In many programming languages you would have to write "0 <
| x and x <= y and y < 1", but in mathematics this kind of
| repetition would quickly become very cumbersome.
___________________________________________________________________
(page generated 2021-10-31 23:01 UTC)