Exponentiation operator precedence

Brendan Eich brendan at mozilla.org
Thu Sep 24 14:17:59 UTC 2015

[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:


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:

1. Give up because the proposal has hit a "wall of confusion".

2. Stick with the current spec,

3. Go back to the old spec, which flouts precedent.

4. 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] :
     delete UnaryExpression_[?Yield]
     void UnaryExpression_[?Yield]
     typeof 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] :
IncrementExpression_[?Yield] ** UnaryExpression_[?Yield]

SimpleUnaryExpression_[Yield] :
     delete SimpleUnaryExpression_[?Yield]
     void SimpleUnaryExpression_[?Yield]
     typeof SimpleUnaryExpression_[?Yield]
     + SimpleUnaryExpression_[?Yield]
     - SimpleUnaryExpression_[?Yield]
     ~ SimpleUnaryExpression_[?Yield]

(It would be nice to rename non-terminals like so: 
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 -(x**y).

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.


Jason Orendorff wrote:
> 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**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".
> -j
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150924/4ef3f04c/attachment.html>

More information about the es-discuss mailing list