Exponentiation operator precedence
On 08/24/2015 10:08, Jason Orendorff wrote:
In math, -x² is -(x²), not (-x)². But as proposed for JS, -x**2 is (-x)**2.
PHP, Python, Haskell, and D side with the traditional algebraic notation, against JS. Here's PHP:
$ php -r 'print(-2 ** 2);' -4
Python:
>>> -2 ** 2 -4
Haskell:
Prelude> -2 ^ 2 -4
The D grammar: dlang.org/grammar.html#UnaryExpression
Let's switch.
Let's not. As I said at the last meeting, making ** bind tighter than unary operators would break x**-2. And making it sometimes tighter and sometimes looser would be too confusing and lead to other opportunities for precedence inversion.
Waldemar
On Mon, Aug 24, 2015 at 5:45 PM, Waldemar Horwat <waldemar at google.com> wrote:
Let's not. As I said at the last meeting, making ** bind tighter than unary operators would break x**-2. And making it sometimes tighter and sometimes looser would be too confusing and lead to other opportunities for precedence inversion.
Don't you think having -x**2
mean the same thing as x**2
is more
confusing? It seems like it will cause problems for the exact
programmers we are trying to help with this feature.
What you're describing as "sometimes tighter and sometimes looser" I would call "the same precedence". It's even easier to specify than the current proposal:
UnaryExpression : PostfixExpression ** UnaryExpression
An expression using both **
and unary -
is then parsed right-associatively:
-a ** -b ** -c ** -d
means -(a ** (-(b ** (-(c ** (-d))))))
On Mon, Aug 24, 2015 at 3:45 PM, Waldemar Horwat <waldemar at google.com> wrote:
On 08/24/2015 10:08, Jason Orendorff wrote:
In math, -x² is -(x²), not (-x)². But as proposed for JS, -x**2 is (-x)**2.
PHP, Python, Haskell, and D side with the traditional algebraic notation, against JS. Here's PHP:
$ php -r 'print(-2 ** 2);' -4
Python:
>>> -2 ** 2 -4
Haskell:
Prelude> -2 ^ 2 -4
The D grammar: dlang.org/grammar.html#UnaryExpression
Let's switch.
Let's not. As I said at the last meeting, making ** bind tighter than unary operators would break x**-2. And making it sometimes tighter and sometimes looser would be too confusing and lead to other opportunities for precedence inversion.
Waldemar
Agreed that the precedence should not bind sometimes tighter and sometimes looser is problematic.
I think following the way other languages solve the same problem is more important for minimizing user surprise than any particular expression looking especially good. Looking kinda similar to other languages in surface syntax has been a major advantage of JS all along.
Dan
On 08/24/2015 17:24, Jason Orendorff wrote:
On Mon, Aug 24, 2015 at 5:45 PM, Waldemar Horwat <waldemar at google.com> wrote:
Let's not. As I said at the last meeting, making ** bind tighter than unary operators would break x**-2. And making it sometimes tighter and sometimes looser would be too confusing and lead to other opportunities for precedence inversion.
Don't you think having
-x**2
mean the same thing asx**2
is more confusing? It seems like it will cause problems for the exact programmers we are trying to help with this feature.What you're describing as "sometimes tighter and sometimes looser" I would call "the same precedence". It's even easier to specify than the current proposal:
UnaryExpression : PostfixExpression ** UnaryExpression
An expression using both
**
and unary-
is then parsed right-associatively:-a ** -b ** -c ** -d means -(a ** (-(b ** (-(c ** (-d))))))
That has different right and left precedence and is probably the closest to the mathematical intent. However, it does carry other surprises. What does each of the following do?
++x ** y; x++ ** y; x ** ++y; x ** y++;
Waldemar
On Mon, Aug 24, 2015 at 7:24 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:
What you're describing as "sometimes tighter and sometimes looser" I would call "the same precedence". It's even easier to specify than the current proposal:
UnaryExpression : PostfixExpression ** UnaryExpression
P.S. Admittedly it might be a good idea to rename "UnaryExpression" if we put a binary operator in there.
On Mon, Aug 24, 2015 at 8:10 PM, Waldemar Horwat <waldemar at google.com> wrote:
That has different right and left precedence and is probably the closest to the mathematical intent.
Not to quibble, but I do want to understand:
UnaryExpression : PostfixExpression ** UnaryExpression
AdditiveExpression : AdditiveExpression + MultiplicativeExpression
We don't say binary +
has different left and right precedence,
right? What's different with **
, apart from being right-associative?
However, it does carry other surprises. What does each of the following do?
Interesting:
++x ** y; // early error: invalid operand for ++
(because it's parsed as `++(x ** y)`)
x++ ** y; // parses as expected: (x++) ** y
x ** ++y; // parses as expected: x ** (++y)
x ** y++; // parses as expected: x ** (y++)
I'm OK with this. It should be rarer in practice than -x**2
, and at
least the language won't silently assign a surprising meaning to user
code.
It could be made to parse as (++x) ** y
by changing prefix ++ and --
to be higher precedence than the other prefix operators. I don't think
this would break any existing programs, because combinations like
++typeof 3
are already early errors. PHP apparently does something
like this:
$ php -r '$x = 3; print(++$x**2 . "\n"); print($x . "\n");'
16
4
D doesn't. Python and Haskell don't have ++/--.
I think we should drop the feature. Given the conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
any solution at this point will confuse many people. These confusions will not result in a confusing static rejection but in runtime behavior that sometimes violates expectations. I was all for adding ** to the language when it did not have these problems. But it does. The minor convenience it adds is not worth these costs. By contrast, no one is confused about the parsing of calls to Math.pow.
When in doubt, leave it out.
On Aug 25, 2015, at 8:11 AM, Mark S. Miller wrote:
I think we should drop the feature. Given the conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
any solution at this point will confuse many people. These confusions will not result in a confusing static rejection but in runtime behavior that sometimes violates expectations. I was all for adding ** to the language when it did not have these problems. But it does. The minor convenience it adds is not worth these costs. By contrast, no one is confused about the parsing of calls to Math.pow.
When in doubt, leave it out.
I've had the same reaction to this recent thread. It seems like the functional form (Math.pow) is a much less confusing formulation for use in JS expressions.
It's interesting to note that while early "algebraic languages" such as FORTRAN, Algol 60, BASIC, PL/I all had exponentiation operators the next couple generations of similar languages including Pascal, C, C++, Java, and C# do not. Each of those languages could have had an exponentiation operator but choose to exclude it.
The utility of an exponentiation operation may simply not be worth the complexity and potential for confusion it introduces into a language with a rich set of operators.
On 8/25/15 at 8:11 AM, erights at google.com (Mark S. Miller) wrote:
any solution at this point will confuse many people. These confusions will not result in a confusing static rejection but in runtime behavior that sometimes violates expectations.
It might be worthwhile flagging as errors constructs which cause frequent confusion. For example, I have been programming in C and C like languages for over 25 years. I still occasionally shoot myself in the foot combining bitwise logical operations and equality tests. If the language forced me to put int parenthesis, I would have been caught at compile time. While I try to put them in as a mater of good programming style, sometimes I forget.
However, there are probably legacy issues.
Cheers - Bill
Bill Frantz | If you want total security, go to prison. There you're 408-356-8506 | fed, clothed, given medical care and so on. The only www.pwpconsult.com | thing lacking is freedom. - Dwight D. Eisenhower
This may be of some relevance: en.wikipedia.org/wiki/Order_of_operations#Special_cases
Exponentiation is clearly exponentially more complicated in terms of order of operations and precedence than other operators if the desire is to follow 'standard maths'. Otherwise, making Exponentiation behave differently in JavaScript to how it does elsewhere is going to be a constant source of bugs and needing parentheses just to get the desire behaviour negates the whole point of making it an operator vs a function.
Thomas
So far, I like the idea of exponentiation having identical precedence to
unary +
/-
, and lower than the increment operators. That sounds like it
should work pretty well.
x ** y // x^y
-x ** y // -(x^y)
x ** -y // x^(-y)
++x ** y === (++x) ** y
x++ ** y === (x++) ** y
x ** ++y === x ** (++y)
x ** y++ === x ** (y++)
I think the following grammar could work. Replace the current (ES2015) PostfixExpression production with:
IncrementExpression:
LeftHandSideExpression
LeftHandSideExpression [no LineTerminator here] ++
LeftHandSideExpression [no LineTerminator here] --
++ LeftHandSideExpression
-- LeftHandSideExpression
And define UnaryExpression as:
UnaryExpression:
IncrementExpression
delete UnaryExpression
void UnaryExpression
typeof UnaryExpression
++ UnaryExpression
+ UnaryExpression
-- UnaryExpression
- UnaryExpression
~ UnaryExpression
! UnaryExpression
IncrementExpression ** UnaryExpression
where the following production (which exists only to avoid to confusingly interpret, e.g., ++x++
as + +x++
):
UnaryExpression:
++ UnaryExpression
-- UnaryExpression
yields a static SyntaxError (or a static ReferenceError if we want to be 100% compatible ES2015).
That way, we have the following expected behaviour:
- in/decrement operators bind most tightly;
- unary and exponentiation operators are applied from right to left.
I like this. It works very well.
It does not work as well as simply omitting ** entirely.
It also does not work. x ** y ** z, if we allow it at all, must be right associative. It must parse as x ** (y ** z).
I'm a python user and I dislike using **, it just becomes rather noisy.
Expressing formulas in text based programming languages has always been kind of a drag. On the other hand, often the mathematical expression of a formula would be quite inefficient because they lack the ability to keep temporary results in some variable. Picking a formula apart to isolate those temporaries the expressiveness vanishes naturally.
Le 25 août 2015 à 19:25, Mark S. Miller <erights at google.com> a écrit :
It also does not work. x ** y ** z, if we allow it at all, must be right associative. It must parse as x ** (y ** z).
Unless I missed something, it is right-associative.
Sorry. I just has another look at your proposed grammar and you are correct.
On Tue, Aug 25, 2015 at 11:12 AM Mark S. Miller <erights at google.com> wrote:
I think we should drop the feature. Given the conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
any solution at this point will confuse many people. These confusions will not result in a confusing static rejection but in runtime behavior that sometimes violates expectations. I was all for adding ** to the language when it did not have these problems. But it does. The minor convenience it adds is not worth these costs. By contrast, no one is confused about the parsing of calls to Math.pow.
Math.pow has exactly the same semantics as the current ** . I'd like the opportunity to work with Claude and Brian to resolve the grammar issue before just tossing it out.
Thanks.
On 08/25/2015 09:38, Claude Pache wrote:
I think the following grammar could work. Replace the current (ES2015) PostfixExpression production with:
IncrementExpression: LeftHandSideExpression LeftHandSideExpression [no LineTerminator here] ++ LeftHandSideExpression [no LineTerminator here] -- ++ LeftHandSideExpression -- LeftHandSideExpression
And define UnaryExpression as:
UnaryExpression: IncrementExpression delete UnaryExpression void UnaryExpression typeof UnaryExpression ++ UnaryExpression + UnaryExpression -- UnaryExpression - UnaryExpression ~ UnaryExpression ! UnaryExpression IncrementExpression ** UnaryExpression
The above is not a valid grammar. For example, parsing ++x leads to a reduce-reduce conflict, where the ++ can come from either a UnaryExpression or an IncrementExpression.
where the following production (which exists only to avoid to confusingly interpret, e.g.,
++x++
as+ +x++
):
That makes no sense. ++ is a lexical token. The lexer always greedily bites off the largest token it can find, even if that leads to a parser error later. The parser does not backtrack into the lexer to look for alternate lexings. For example,
x +++++ y;
is a syntax error because it's greedily lexed as:
x ++ ++ + y;
The parser does not backtrack into the lexer to look for other possible lexings such as:
x ++ + ++ y;
UnaryExpression: ++ UnaryExpression -- UnaryExpression
yields a static SyntaxError (or a static ReferenceError if we want to be 100% compatible ES2015).
This is a problem. It makes ++x into a static SyntaxError because x is a UnaryExpression.
If you got rid of these two ++ and -- productions in UnaryExpression, that would solve that problem (and I think make the grammar valid).
Waldemar
Le 25 août 2015 à 03:22, Jason Orendorff <jason.orendorff at gmail.com> a écrit :
On Mon, Aug 24, 2015 at 7:24 PM, Jason Orendorff <jason.orendorff at gmail.com> P.S. Admittedly it might be a good idea to rename "UnaryExpression" if we put a binary operator in there.
-j
"RightAssociativeExpression"?
When the costs were minor, it was ok that the benefits were minor. Given significant costs, we need to ask:
Why do we need ** ? What great benefit does it provide? If nothing compelling, then this proposal has lost consensus.
I would have to agree with Mark on this one. I have yet to find anything compelling for an exponentiation operator. I know multiple things that would be more interesting, such as the proposed bind operator.
Several languages have it, but I don't think "it's in several other languages, so it has to be in this language" is enough to merit an addition to the language. And in my honest opinion, it doesn't look nice, either.
Le 26 août 2015 à 00:43, Mark S. Miller <erights at google.com> a écrit :
When the costs were minor, it was ok that the benefits were minor. Given significant costs, we need to ask:
While I don't have a strong opinion about the cost of the proposed modified grammar, I protest that the cost of the previous version wasn't anything near minor (although it was probably an oversight): having -x**y
producing (literally) the opposite result of what is expected, and even only half of the time, is a high cost in terms of bugs produced and debugging man-hours lost.
I completely agree. My "When the costs were minor" refers to when we were not yet aware of the conflict.
On Tue, Aug 25, 2015 at 5:43 PM, Mark S. Miller <erights at google.com> wrote:
When the costs were minor, it was ok that the benefits were minor.
The costs will probably still be minor if we just let Rick look at it and revise the proposal.
What has happened here is
- upon implementing the feature, we noticed a problem
- we thought through it together and found possible solutions
- we found other languages already use these solutions
This seems like less turbulence than average for a new ES feature, even a minor one. Considering dropping the feature seems premature.
I don't get it. The conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
seems unresolvable. By the first bullet, -2 ** 2 would be -4. By the second, it would be 4. Either answer will surprise too many programmers. By contrast, no one is confused by either -Math.pow(2, 2) or Math.pow(-2, 2).
Yehuda Katz cited an acronym taught when he was a wee lad learning algebra: PEMDAS (Parentheses, Exponentiation, Multiplication/Dviistion, Addition/Subtraction). Who else learned this?
There's nothing sacrosanct about binary precedence being generally lower than unary. Consider the property access operators in JS. But the precedent to which all cited languages bow is Math and that's what programmers (mostly) study. I think you are making too much out of the local -x ** y case in light of this global argument.
There's still the issue of exponentiation being right-associative. Unless ** becomes an operator which behaves differently as to how it would in a high school maths class, we're at an impasse.
That said, ^ is usually the operator used for exponentiation outside programming languages when you need to express an equation in text. It could be made explicit that ** is a variant on 'exponentiation', but then maybe things are deviating from being useful.
Thomas
On Wed, Aug 26, 2015 at 11:09 AM, Mark S. Miller <erights at google.com> wrote:
I don't get it. The conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
seems unresolvable. By the first bullet, -2 ** 2 would be -4. By the second, it would be 4. Either answer will surprise too many programmers.
I just think the danger is not so great. Who's really going to be
surprised that -x**2
is negative? This follows not only other
mainstream programming languages but a mathematical notation that has
been in common use for hundreds of years and is taught to every high
school algebra student.
On Wed, Aug 26, 2015 at 1:03 PM, Thomas <thomasjamesfoster at bigpond.com> wrote:
There's still the issue of exponentiation being right-associative. Unless ** becomes an operator which behaves differently as to how it would in a high school maths class, we're at an impasse.
I'm not sure I follow. Exponentiation is right-associative in math, in the current ** proposal, and in every suggested update to it.
On Aug 26, 2015, at 9:09 AM, Mark S. Miller wrote:
I don't get it. The conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
seems unresolvable. By the first bullet, -2 ** 2 would be -4. By the second, it would be 4. Either answer will surprise too many programmers. By contrast, no one is confused by either -Math.pow(2, 2) or Math.pow(-2, 2).
An of course the history includes many very popular languages that chose to not include an exponentiation operator.
This is really a cost-benefits issue. Several of us have identified costs (complexity, cognitive, pedagogical, error hazards, etc.). What are the benefits of having such an operator? Are they sufficient to offset the cost of having it.
Exponentiation is written in conventional mathematics as if it were a postfix unary operator, parameterised by a value written in superscript. IMO this puts it in a whole different class to binary operators where both operands are written equally. I don't see a ** b ** c as a good reflection of mathematical convention.
Number.prototype.pow, on the other hand, would be fine.
On 08/26/2015 09:09, Mark S. Miller wrote:
I don't get it. The conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
seems unresolvable. By the first bullet, -2 ** 2 would be -4. By the second, it would be 4. Either answer will surprise too many programmers. By contrast, no one is confused by either -Math.pow(2, 2) or Math.pow(-2, 2).
The grammar concerns have been resolved nicely upthread, so I'm not sure what your objection is. The costs are no more significant than in the original proposal. ** now has the same precedence as unary operators and weaker than the increment operators, which matches what most other languages that support exponentiation do.
There is precedence for unary operators not always binding tighter than binary. yield 3+4 is yield(3+4), not (yield 3)+4.
Waldemar
On Wed, Aug 26, 2015 at 2:55 PM, Waldemar Horwat <waldemar at google.com>
wrote:
On 08/26/2015 09:09, Mark S. Miller wrote:
I don't get it. The conflict between
- the history of ** in other languages,
- the general pattern that unary binds tighter than binary
seems unresolvable. By the first bullet, -2 ** 2 would be -4. By the second, it would be 4. Either answer will surprise too many programmers. By contrast, no one is confused by either -Math.pow(2, 2) or Math.pow(-2, 2).
The grammar concerns have been resolved nicely upthread, so I'm not sure what your objection is. The costs are no more significant than in the original proposal. ** now has the same precedence as unary operators and weaker than the increment operators, which matches what most other languages that support exponentiation do.
There is precedence for unary operators not always binding tighter than binary. yield 3+4 is yield(3+4), not (yield 3)+4.
The force of that precedent is indeed what my objection is. The "yield" counter-example is interesting, but "yield" is an identifier not an operator symbol, and so does not as clearly fall within or shape operator expectations.
If someone explains a compelling need for ** I would find that interesting. But until then...
Alexander Jones schrieb:
Exponentiation is written in conventional mathematics as if it were a postfix unary operator, parameterised by a value written in superscript. IMO this puts it in a whole different class to binary operators where both operands are written equally. I don't see a ** b ** c as a good reflection of mathematical convention.
I disagree. When in maths we write x <sup> y <sup> z </sup></sup>, we
mean x ^ (y ^ z). Which is exactly what x ** y ** z
will do.
And no, we never write x <sup>y</sup> <sup>z</sup>, if we wanted to
express that we'd write x <sup>y * z</sup> (or often enough, with
implicit multiplication, i.e. no * operator: x <sup>y z</sup>).
If we'd want to express that in JS, it would by x ** (y * z).
Number.prototype.pow, on the other hand, would be fine.
You don't mean to use it like x.pow(y).pow(z)
, do you? Sure, a
function or method invocation is always explicit with parenthesis. But
that's no improvement over the current Math.pow
.
Bergi
On 08/26/2015 15:08, Mark S. Miller wrote:
The force of that precedent is indeed what my objection is. The "yield" counter-example is interesting, but "yield" is an identifier not an operator symbol, and so does not as clearly fall within or shape operator expectations.
If someone explains a compelling need for ** I would find that interesting. But until then...
** is a convenience, and that's the wrong criterion to apply here. If it were, then we wouldn't have useful conveniences like Math.cosh or arrow functions.
I'd rather read
ax**3 + bx**2 + c*x + d
than
aMath.pow(x, 3) + bMath.pow(x, 2) + c*x + d
Waldemar
On Wed, Aug 26, 2015 at 6:19 PM, Waldemar Horwat <waldemar at google.com>
wrote:
On 08/26/2015 15:08, Mark S. Miller wrote:
The force of that precedent is indeed what my objection is. The "yield" counter-example is interesting, but "yield" is an identifier not an operator symbol, and so does not as clearly fall within or shape operator expectations.
If someone explains a compelling need for ** I would find that interesting. But until then...
** is a convenience, and that's the wrong criterion to apply here. If it were, then we wouldn't have useful conveniences like Math.cosh or arrow functions.
I'd rather read
ax**3 + bx**2 + c*x + d
than
aMath.pow(x, 3) + bMath.pow(x, 2) + c*x + d
Ok, we have a benefit to evaluate. Brevity. With the example contrast between
a*x**3 + b*x**2 + c*x + d
and
a*Math.pow(x, 3) + b*Math.pow(x, 2) + c*x + d
Let's also apply Alexander's suggestion
a*x.pow(3) + b*x.pow(2) + c*x + d
To help us compare for brevity, and because I'm too lazy to count, I'm sending it in a fixed width font.
Is there also perhaps a potential performance/static analysis benefit in
having it be syntax, rather than a builtin function? Math.pow
can be
overwritten, and varies per-realm, but **
can not and does not.
Not much, as far as I can tell. Engines do usually lower this, and redo the whole object when the shape changes, or an intrinsic no longer applies. V8 has a MathPow intrinsic, and I believe SpiderMonkey has similar.
x ** y ** z is easier to read/write than x.pow(y.pow(z))
On 8/27/15 at 11:51 PM, niloy.mondal84 at gmail.com (Niloy Mondal) wrote:
x ** y ** z is easier to read/write than x.pow(y.pow(z))
As a language guru, you know which operation will be performed first. As Joe programmer, I don't and I would need to write it as x ** (y ** z).
With some operations, like +, the order doesn't matter and x + y
- z is not confusing.
Cheers - Bill
Bill Frantz |"We used to quip that "password" is the most common 408-356-8506 | password. Now it's 'password1.' Who said users haven't www.pwpconsult.com | learned anything about security?" -- Bruce Schneier
You don't need to be a language guru to know which operation will be performed first, you can (and should) check an operator precedence and associativity table such as MDN's developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence . Knowing operator precedence and associativity is very important when reading code written by others, and many projects use the "no unnecessary parens" linting rule.
x ** y ** z is easier to read/write than x.pow(y.pow(z))
That might be cherry picking. Trying to make up something a little more complex:
a**b * -c**d + e
Math.pow(a, b) * -Math.pow(c, d) + e
a.pow(b) * -c.pow(d) + e
I don't have strong feelings on this issue, but the third option looks pretty good to me. If we had some form of pipelining syntax (yet to be proposed), then we could have:
let { pow } = Math;
a->pow(b) * -c->pow(d) + e;
On Thu, Aug 27, 2015 at 9:04 AM, Kevin Smith <zenparsing at gmail.com> wrote:
a**b * -c**d + e
I don't think people use unary - like this very often. It's nothing to do with exponentiation. You don't see people write:
a * -c + e
because the right-side-up way to say that is:
e - a * c
because the right-side-up way to say that is:
e - a * c
Yeah, I was waiting for someone to point that out, after I hit send. : ) I should spend more time setting up a better examples...
Ideally syntax proposals should include some frequency information to motivate any change. Is there an easy search to estimate the frequency of Math.pow? In my application codebase (financial app with only modest JS use), there are very few uses, and there are as many uses of Math.sin as there are of Math.pow.
Anecdotally, my eyes caught on: "-Math.pow(2,-10*a/1)" (from a charting library) which makes me not want to have to review code where I'm worried about the precedence of exponentiation.
On 08/27/2015 09:25 AM, Dean Tribble wrote:
Ideally syntax proposals should include some frequency information to motivate any change. Is there an easy search to estimate the frequency of Math.pow? In my application codebase (financial app with only modest JS use), there are very few uses, and there are as many uses of Math.sin as there are of Math.pow.
Frequency relative to what, though? If code that does nontrivial math is a very small proportion of total JS code, and yet the exponentiation operator makes that code much more readable, then what is the conclusion? I would argue that ** precedence confusion is irrelevant to code that isn't going to use Math.pow in the first place. So it's a question of whether ** is a big enough readability win in code that computes exponents.
Anecdotally, my eyes caught on: "-Math.pow(2,-10*a/1)" (from a charting library) which makes me not want to have to review code where I'm worried about the precedence of exponentiation.
I'd have to write that out: -2**(-10*a/1). That doesn't seem too bad.
For myself, I do very much prefer Math.sqrt(a2 + b2) to Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)). The verbosity and uneven density of notation is really bothersome -- for any equation like the second one, I guarantee that I'll rewrite it on paper to figure out what it's saying. (Ok, maybe not for that specific formula, but even there I'll mentally render it.) I would not need to do so with the first one. Jumping between prefix and infix is jarring.
Then again, I could make the same argument for Math.sqrt(a2 + b2) vs (a2 + b2) ** 0.5. And I don't like the second one much. But people don't interchange those when handwriting formulas, either.
Math.sqrt(a.pow(2) + b.pow(2)) is an interesting middle point. I initially thought it struck the right balance, but seeing it written out, it still looks far inferior to me.
A more complex example might help:
a * (b - a)**(x - 1/2 * (b - a)**2)
vs
a * Math.pow(b - a, x - 1/2 * Math.pow(b - a, 2))
vs
a * (b - a).pow(x - 1/2 * (b - a).pow(2))
For me, the middle one is a mess. I can't make sense of it, and I can't spot the common (b - a) expression at all. The first one is as readable as such formulas ever are when written out with ASCII text. The third one is somewhere in between. I can see the common (b - a), and perhaps if I got more used to seeing .pow I could mentally make use of it without writing it on paper, but for now I cannot. Part of the problem is that I can easily translate "x**2" into "x squared", but "x.pow(2)" is raising x to the power of 2.
Anecdotal evidence via a quick github search search?l=JavaScript&p=1&q=Math.pow+language%3AJavaScript+extension%3Ajs&ref=advsearch&type=Code&utf8=✓
A significant number of the usages are unit tests. Many others are repeats from popular libraries like D3.
The most extreme use case I could find: y4ashida/ika/blob/master/js/culc_dence.js y4ashida/ika/blob/master/js/culc_dence.js
Long-time esdiscuss lurker; hopefully this perspective is helpful.
I think the problem here is that traditional mathematic notation uses visual cues to imply precedence that JS can't take advantage of. When -3 ** 2 is written out on paper, the 2 is very clearly grouped visually with the 3. In fact, the superscript almost makes the 2 feel like an appendage of the 3. That makes it more natural to read it as two items: the negative sign, and (3 ** 2).
By contrast, when (-3 ** 2) is written out in code, the negative sign is way closer visually to the 3 than the 2 is, so I find myself instinctively pulling out a "-3" first and reading the expression as (-3)**2.
Treating -3 ** 2 as -(3 ** 2) seems technologically possible and mathematically sensible, but also like it's going against the grain of the human visual system. I think that's at least one reason to value the "binary precedence should be lower than unary" principle.
The Number.prototype.pow
approach might be an improvement, as it has the
effect of mandating parentheses on some cases that might otherwise be
confusing (e.g. x ** y ** z) and it offers most of the conciseness of
**. But -x.pow(2) still feels unpredictable to me as an everyday programmer
switching between languages. (Also, pow() requires an extra set of
parentheses if I want to operate on a literal.)
Maybe it's ok if the operator surprises some people in some cases, and the guidance will just become to use parentheses if you're unsure. That occasional uncertainty for the vast majority of JS programmers that aren't doing much exponentiation might be worth it if ** makes a minority of JS programmers much more productive. I don't know.
On 08/27/2015 11:20 AM, Ethan Resnick wrote:
Long-time esdiscuss lurker; hopefully this perspective is helpful.
I think the problem here is that traditional mathematic notation uses visual cues to imply precedence that JS can't take advantage of. When -3 ** 2 is written out on paper, the 2 is very clearly grouped visually with the 3. In fact, the superscript almost makes the 2 feel like an appendage of the 3. That makes it more natural to read it as two items: the negative sign, and (3 ** 2).
By contrast, when (-3 ** 2) is written out in code, the negative sign is way closer visually to the 3 than the 2 is, so I find myself instinctively pulling out a "-3" first and reading the expression as (-3)**2.
If we're making ** bind tighter than unary -, then I would hope it would be written -3**2, not -3 ** 2. The latter is indeed deceptive.
For me, xyz is rare enough that I don't really care if ** is right associative or nonassociative. Parentheses are part of the cost you have to pay for rendering things as plain text -- and yet, I see no reason not to make xyz just do the right thing.
Ethan is making my point far better than I did, and I agree completely about the issue of unary operators visually appearing more tightly bound than binary operators.
At this point it seems fair to at least acknowledge the prospect of significant whitespace.
-x**2 === -(x ** 2)
-x ** 2 === (-x) ** 2
On 08/27/2015 11:58, Alexander Jones wrote:
Ethan is making my point far better than I did, and I agree completely about the issue of unary operators visually appearing more tightly bound than binary operators.
At this point it seems fair to at least acknowledge the prospect of significant whitespace.
-x**2 === -(x ** 2) -x ** 2 === (-x) ** 2
Take a look at the Fortress language ☺. But that one benefits from operators and syntax not limited to ASCII.
Waldemar
On Thu, Aug 27, 2015 at 2:58 PM, Alexander Jones <alex at weej.com> wrote:
Ethan is making my point far better than I did, and I agree completely about the issue of unary operators visually appearing more tightly bound than binary operators.
At this point it seems fair to at least acknowledge the prospect of significant whitespace.
-x**2 === -(x ** 2) -x ** 2 === (-x) ** 2
One kind of cost that I haven't seen mentioned (and is relevant re:whitespace) is the impact on minifiers and other tools that use JS as output, which have to deal with operator precedence/whitespace rules in quite complicated ways. There's a nice recent piece about precedence bugs in a minifier:
zyan.scripts.mit.edu/blog/backdooring-js
Making the creation and maintenance of systems like minifiers harder is a real cost worth bearing in mind when updating already-subtle existing rules.
Don't rely on github searches to turn up representative examples. It doesn't work that well. Here's my educated guess as to how ** will be used.
The most common use will be to square numbers.
a²
a**2
Math.pow(a, 2)
a.pow(2)
Currently you might write a * a
, which is kind of lame.
So where's the benefit? If this trivial thing is the most common use of exponentation, why bother?
The ability to look at an expression and understand it at a glance is a big deal.
x² + y² > limit
x**2 + y**2 > limit
Math.pow(x, 2) + Math.pow(y, 2) > limit
x.pow(2) + y.pow(2) > limit
A big big deal. It's the reason we have arithmetic operators in JS in the first place.
Exponentiation is common when computing easing functions, curves for
graphics, interest, simulations, random stuff in games. Nth roots are
fairly common too (apr**(1/12)
). In all of these cases, the user is
doing the same thing: translating a mathematical formula they wish to
use from "math" to JS. It is not extra hard to translate such a
formula using Math.pow(), but it is harder to read once you're done.
You have to mentally translate back to "math".
Mark S. Miller wrote:
but "yield" is an identifier not an operator symbol, and so does not as clearly fall within or shape operator expectations.
delete typeof
These are high-precedence, same level as unary +/- etc. Unlike yield, which is at assignment operator level.
I'm simply questioning your "identifier not an operator symbol" razor here, when previously you argued "unary operators have higher precedence" without distinguishing the identifier-named unary ops of high precedence.
Not to worry, the significant whitespace prospect was (I trust) a warding-off spell. Good of Waldemar to mention Fortress, too.
JS, which as source is and will always be minified, indeed requires full-parsing minifiers, so one might naively still entertain the stated prospect. But it's a bad idea, since people minify (or just tidy by removing spaces) by hand. Keep warding off significant space!
I agree with that completely.
This would also become a gotcha. I can see a lot of people down the road thinking -x ** 2 === -x**2, only to find that's not the case. It looks like it should, which will quickly lead to hard to find bugs. I think it's a terrible idea, but that's just my opinion.
Quick update from TC39 yesterday where Rick and I presented the Stage 3 Exponentiation Operator proposal:
rwaldron.github.io/exponentiation-operator
The current spec, revised to match precedent from all known programming languages that have exponentiation operators, binds
-x^y = -(x^y )
and not
-x^y = (-x)^y
as the original proposal specified. These examples use Math notation, but the proposed exponentiation operator is infix ** of course. And that's the source of trouble for this proposal.
The problem is, however rare unary minus before an exponentiation expression may be, the lack of superscript-with-smaller-font sugests that - binds tighter than **. And indeed apart from dot (a special form whose right operand must be a lexical identifier-name) and square brackets (which isn't an infix operator per se), unary operators bind tighter than binary in JS as in C and other C-derived languages.
Yehuda suggested that exponentiation was "cargo-culted" from Math into Fortran, and then into other languages, without considering the notational shift and the apparent precedence inversion. I don't know the history, but it's plausible, and numerics folks probably would not expect unary - to bind tighter than exponentiation. But JS programmers may see -x and especially -2 as a tighter expression, if not a single lexeme, than any expression joined by infix **.
We debated the options, which I think are four in number:
-
Give up because the proposal has hit a "wall of confusion".
-
Stick with the current spec,
-
Go back to the old spec, which flouts precedent.
-
Make unparenthesized exponentiation expression as operand of unary operators an early error.
I came up with (4) late in the day, and it didn't get a fair hearing. Before I wrote it up on the board, I asked for a straw poll (those can bite back by forcing people onto a bandwagon, as Dave Herman pointed out) on (1-3). The poll favored (2), with notable but minority positions for (1) and (3).
The grammar change needed for (4) is trivial. Instead of
UnaryExpression_[Yield] : IncrementExpression_[?Yield] delete UnaryExpression_[?Yield] void UnaryExpression_[?Yield] typeof UnaryExpression_[?Yield]
- UnaryExpression_[?Yield]
- UnaryExpression_[?Yield] ~ UnaryExpression_[?Yield] !UnaryExpression_[?Yield] IncrementExpression_[?Yield] ** UnaryExpression_[?Yield]
we factor to require parentheses around the last right-hand side if it is an operand of a unary operator:
UnaryExpression_[Yield] : SimpleUnaryExpression_[?Yield] IncrementExpression_[?Yield] ** UnaryExpression_[?Yield]
SimpleUnaryExpression_[Yield] : IncrementExpression_[?Yield] delete SimpleUnaryExpression_[?Yield] void SimpleUnaryExpression_[?Yield] typeof SimpleUnaryExpression_[?Yield]
- SimpleUnaryExpression_[?Yield]
- SimpleUnaryExpression_[?Yield] ~ SimpleUnaryExpression_[?Yield] !SimpleUnaryExpression_[?Yield]
(It would be nice to rename non-terminals like so: s/<UnaryExpression>/ExponentiationExpression/g;
s/<SimpleUnaryExpression>/UnaryExpression/g where <> are left and
right word boundaries -- but I'm leaving this out for now since it touches more of the spec and is merely a nominal change.)
Thus one may write
let z = K - x**y;
without having to parenthesize unduly, but one cannot write
let z = -x ** y;
The user is forced by an early error to write either (-x)y or -(xy).
The early error stops parsing with a thrown SyntaxError.
It seems to me (4) wins and rescues the proposal at stage 3 from suffering a loss of consensus. Comments welcome.
[Apologies for resending, trying to fix formatting of grammar excerpts. /be]
Quick update from TC39 yesterday where Rick and I presented the Stage 3 Exponentiation Operator proposal:
rwaldron.github.io/exponentiation-operator
The current spec, revised to match precedent from all known programming languages that have exponentiation operators, binds
-x^y = -(x^y )
and not
-x^y = (-x)^y
as the original proposal specified. These examples use Math notation, but the proposed exponentiation operator is infix ** of course. And that's the source of trouble for this proposal.
The problem is, however rare unary minus before an exponentiation expression may be, the lack of superscript-with-smaller-font sugests that - binds tighter than **. And indeed apart from dot (a special form whose right operand must be a lexical identifier-name) and square brackets (which isn't an infix operator per se), unary operators bind tighter than binary in JS as in C and other C-derived languages.
Yehuda suggested that exponentiation was "cargo-culted" from Math into Fortran, and then into other languages, without considering the notational shift and the apparent precedence inversion. I don't know the history, but it's plausible, and numerics folks probably would not expect unary - to bind tighter than exponentiation. But JS programmers may see -x and especially -2 as a tighter expression, if not a single lexeme, than any expression joined by infix **.
We debated the options, which I think are four in number:
-
Give up because the proposal has hit a "wall of confusion".
-
Stick with the current spec,
-
Go back to the old spec, which flouts precedent.
-
Make unparenthesized exponentiation expression as operand of unary operators an early error.
I came up with (4) late in the day, and it didn't get a fair hearing. Before I wrote it up on the board, I asked for a straw poll (those can bite back by forcing people onto a bandwagon, as Dave Herman pointed out) on (1-3). The poll favored (2), with notable but minority positions for (1) and (3).
The grammar change needed for (4) is trivial. Instead of
UnaryExpression_[Yield] : IncrementExpression_[?Yield] delete UnaryExpression_[?Yield] void UnaryExpression_[?Yield] typeof UnaryExpression_[?Yield] + UnaryExpression_[?Yield] - UnaryExpression_[?Yield] ~ UnaryExpression_[?Yield] !UnaryExpression_[?Yield] IncrementExpression_[?Yield] ** UnaryExpression_[?Yield]
we factor to require parentheses around the last right-hand side if it is an operand of a unary operator:
UnaryExpression_[Yield] : SimpleUnaryExpression_[?Yield] IncrementExpression_[?Yield] ** UnaryExpression_[?Yield]
SimpleUnaryExpression_[Yield] : IncrementExpression_[?Yield] delete SimpleUnaryExpression_[?Yield] void SimpleUnaryExpression_[?Yield] typeof SimpleUnaryExpression_[?Yield] + SimpleUnaryExpression_[?Yield] - SimpleUnaryExpression_[?Yield] ~ SimpleUnaryExpression_[?Yield] !SimpleUnaryExpression_[?Yield]
(It would be nice to rename non-terminals like so: s/<UnaryExpression>/ExponentiationExpression/g;
s/<SimpleUnaryExpression>/UnaryExpression/g where <> are left and
right word boundaries -- but I'm leaving this out for now since it touches more of the spec and is merely a nominal change.)
Thus one may write
let z = K - x**y;
without having to parenthesize unduly, but one cannot write
let z = -x ** y;
The user is forced by an early error to write either (-x)y or -(xy).
The early error stops parsing with a thrown SyntaxError.
It seems to me (4) wins and rescues the proposal at stage 3 from suffering a loss of consensus. Comments welcome.
Here's a nicely formatted jsbin version of my message:
Sorry about the mess, mail user agents (at least Postbox) and archive software do not like explicit indentation.
I like #4. Normally in a situation like this I would still argue for #1. #4 is a complicated special case that breaks the normal pattern of operator precedence elsewhere in the language. The need for ** is not great enough to justify introducing a new special case for users to learn.
However, in this case, #4 is only technically complicated -- for those writing or reading spec docs like us. For normal users, the only complexity is a rarely encountered surprising static error. With a decent (and easy to generate) error message, these users will immediately know what they need to do to repair their program.
Significant programs are read much more than they are written. Both #2 and .#3 will lead many readers to misread programs. For programs that are not rejected, #4 is no more confusing than #1. Altogether, for readers, #4 is better than #1 because ** is more readable than Pow.
+1 on #4.
On 24 September 2015 at 17:19, Brendan Eich <brendan at mozilla.org> wrote:
Even nicer:
I hate email.
You are holding it wrong.
I won't try to guess where the rendering problem is, but see the attached screenshots. This is how I'm seeing your page on my Chrome and Firefox.
Thanks again to Brendan for taking time to write this up. And to Mark, thanks for reviewing this and expeditiously providing valuable feedback—it's greatly appreciated.
Le 24 sept. 2015 à 16:11, Brendan Eich <brendan at mozilla.org> a écrit :
And indeed apart from dot (a special form whose right operand must be a lexical identifier-name) and square brackets (which isn't an infix operator per se), unary operators bind tighter than binary in JS as in C and other C-derived languages.
I just wonder why it is important that unary binds tighter? For instance, before I carefully studied the issue of this thread, I have never expected that unary minus binds tighter than binary multiplication operator in expressions like -2*x
(although it does not matter in that case).
without having to parenthesize unduly, but one cannot write
let z = -x ** y;
The user is forced by an early error to write either (-x)y or -(xy).
In traditional math notation, when you mean (-x)**n
, you write (-x)ⁿ with mandatory parentheses, so I don’t expect that many people will be tempted to miswrite it -x ** n
.
Making the parentheses mandatory here will be somewhat annoying in perfectly reasonable expressions, where you usually don’t use parentheses in real math notation., like:
let s2 = - x**2 - y**2 - z**2 + t**2
On Thu, Sep 24, 2015 at 11:08 AM, Claude Pache <claude.pache at gmail.com>
wrote:
Le 24 sept. 2015 à 16:11, Brendan Eich <brendan at mozilla.org> a écrit :
And indeed apart from dot (a special form whose right operand must be a lexical identifier-name) and square brackets (which isn't an infix operator per se), unary operators bind tighter than binary in JS as in C and other C-derived languages.
I just wonder why it is important that unary binds tighter? For instance, before I carefully studied the issue of this thread, I have never expected that unary minus binds tighter than binary
Before Jason pointed out the discrepancy:
- all of us on the committee who were engaged with the proposal
- including myself,
- all those who reviewed the proposal,
- and all those who implemented the proposal had the opposite naive expectation. That's the point. In the absence of learning about this case specifically, many people will be unpleasantly surprised by #2, and many by #3. Therefore #4 wins. (Actually, it just won ;).)
Right. It's confusing because (as opposed to Math), ** is not a superscripting operator whose typography suggests higher precedence than -, and this matters for the sign of the result when exponentiating.
Claude, the Math folks won't often need to negate the result, but when they do, they'll have to parenthesize. That's the price of the typographic shift and the precedence inversion that it suggests to many people.
I object to #4. Disallowing perfectly reasonable math expressions (Claude's is a good example) makes this operator too surprising to include in the language.
Sez you! :-P
Seriously, the problem you are dismissing implicitly (bad form :-/) is the one we discussed yesterday, which I've stated explicitly twice now: the typography and plain sense of JS-in-itself suggests unary minus binds tighter than any binary connective (including **).
Saying "Math!" doesn't overcome this, any more than shouting "typography!" or "JS-in-itself!"
You don't get to pick a winner just because you root for one team. If you think the operator is worth adding and you concede that the other team's position is tenable because enough people are confused on the committee to predict a wider problem, then you have to consider whether the hardship of mandatory parens in the rare case of negated exponentiation expression is onerous.
It's not a source of runtime bugs, where test coverage is never good enough. It's a SyntaxError, so (a) Math uber alles people who (b) run into the rare hard case will learn to parenthesize.
I suspect that many Math-should-prevail types (I am among them in the abstract, prior to engaging with the human factors at play in JS) will want to parenthesize anyway, given the typographic appearance of precedence inversion. I will certainly do it if there's any unary prefixing an exponentiation expression in my code.
I did find a formula search engine. See www.searchonmath.com/result?equation=-+x^{y}&page=1&tm=1 for a taste of the hard cases (and false positives, which do not count).
Methinks you protest too much. Where is the common case made hard by proposal (4)?
Le 24 sept. 2015 à 22:20, Brendan Eich <brendan.eich at gmail.com> a écrit :
Sez you! :-P
Seriously, the problem you are dismissing implicitly (bad form :-/) is the one we discussed yesterday, which I've stated explicitly twice now: the typography and plain sense of JS-in-itself suggests unary minus binds tighter than any binary connective (including **).
It’s more a question of convention than of typography, as in: x + y * z
or: x || y && z
.
There are two conflicting conventions here, not two conflicting typographies. For the set of operators defined in C, the convention of making every unary operator having a higher precedence, does not conflict with the conventions used in math. (It is not clear for me why that convention is important, just that it is a handy uniform rule that works well for some limited set of operators.)
Note that if you want to disallow -x**y
, you might want to disallow x**y**z
(which is imho more confusing), as there is no other right-associative binary operator (except assignment).
The worry over the meaning of (- x ** y) arises not just due to conventions or inference from typography, but from intuitions based on limited knowledge. As Dave Herman pointed out yesterday, many programmers (most) do not have the full precedence and associativity set memorized. When in doubt, they will rely on precedent in the language, visual cues including weight of operator (two chars vs. one), unary vs. binary. When in more doubt, they'll over-parenthesize.
The problem case is exactly the user cohort who'll not have enough doubt to over-parenthesize, who will rely on precedent and typographic weight clues, sound or not.
Mark Miller pointed out how many reviewers missed the problem with the original proposal as predicting likely confusion at scale. If we can avoid any confusion with an error, and force the people who should over-parenthesize to do so, while taxing the Math fans to do it when they must as well, this seems more prudent than just sticking with the Math-trumps-JS precedent and letting wrong results pass silently at runtime.
(I see no reason to disallow right-associative chaining. We haven't run head-on into confusion by drafting it the wrong way and getting it to stage 3 :-P.)
My preference is for 2, but I don't have objections to 4. Either works.
One advantage of #4 is that it will make it possible to introduce either #2 or #3 in the future, if we change our minds. This way we can use real world use cases to decide if we should go for #2 or #3.
Claude Pache wrote:
I just wonder why it is important that unary binds tighter? For instance, before I carefully studied the issue of this thread, I have never expected that unary minus binds tighter than binary multiplication operator in expressions like
-2*x
(although it does not matter in that case).Making the parentheses mandatory here will be somewhat annoying in perfectly reasonable expressions, where you usually don’t use parentheses in real math notation., like:
let s2 = - x**2 - y**2 - z**2 + t**2
I would overcome it and do not write the parens:
let s2 = 0 - x**2 - y**2 - z**2 + t**2
Writing mandatory parens here is ugly.
In fact, I am surprised "-2" is unary minus with 2, I thought it is number -2. And similarly to Claude, I always read -xy in math notation, that is, as -(xy). Luckily, for multiplication it does not matter.
An off-topic thought: Unary minus (and plus) are only used with numbers in JS. Why are they treated specially, not as hidden 0+x and 0-x, respectively? That would be logical (unary plus and minus would have same precendence as binary plus and minus).
Because C (B, BCPL, Algol). Too late to change JS where people do tricks such as !-x. No win in risking compat break.
On Thu, Sep 24, 2015 at 8:14 AM, Mark S. Miller <erights at google.com> wrote:
I like #4. Normally in a situation like this I would still argue for #1. #4 is a complicated special case that breaks the normal pattern of operator precedence elsewhere in the language. The need for ** is not great enough to justify introducing a new special case for users to learn.
However, in this case, #4 is only technically complicated -- for those writing or reading spec docs like us. For normal users, the only complexity is a rarely encountered surprising static error. With a decent (and easy to generate) error message, these users will immediately know what they need to do to repair their program.
Significant programs are read much more than they are written. Both #2 and #3 will lead many readers to misread programs. For programs that are not rejected, #4 is no more confusing than #1. Altogether, for readers, #4 is better than #1 because ** is more readable than Pow.
MarkM, I'm surprised you didn't also mention that there is precedent for #4 from your own E.
The E language chose to place math operations as methods on numbers, rather than on any static "Math" object, and does not have an exponentiation operator. In order to avoid precedence surprises of the category we're discussing, E statically rejects the combination of a unary prefix (negation) and unary postfix (method call) operator.
-(2).pow(2) # "ought to be" -4, is a syntax error
-(2).max(2) # "ought to be" 2, is a syntax error
(The parentheses around the number are not actually required in E, but I have included them for the sake of comparison to JS despite the lexical rejection of "1.foo" in JS.)
JavaScript already syntactically accepts the above programs (parsing "-" as lower precedence than ".foo()"), but #4 is in the same spirit of rejecting cases where there are conflicting or unclear precedents for operator precedence.
In math, -x² is -(x²), not (-x)². But as proposed for JS, -x**2 is (-x)**2.
PHP, Python, Haskell, and D side with the traditional algebraic notation, against JS. Here's PHP:
Python:
Haskell:
The D grammar: dlang.org/grammar.html#UnaryExpression
Let's switch.
Another case to think about is
-2 ** -2
. In Haskell, that's a syntax error, but the other three languages all treat it the same way, so maybe JS should follow suit.