elm / compiler

Compiler for Elm, a functional language for reliable webapps.
https://elm-lang.org/
BSD 3-Clause "New" or "Revised" License
7.51k stars 656 forks source link

Operator precedence in negative exponentials #1530

Open drathier opened 7 years ago

drathier commented 7 years ago

-2^2 evaluates to a positive number, unlike in (most) mathematics, since unary - has higher precedance than ^ in Elm.

I expected it to evaluate to a negative number. It is extra easy to make a mistake since -2^n changes sign for odd/even n, so half the numbers seem correct.

Can we change the operator precedance?

EDIT: it should fit nicely between ^ and *, like D-lang and Python does it.

process-bot commented 7 years ago

Thanks for the issue! Make sure it satisfies this checklist. My human colleagues will appreciate it!

Here is what to expect next, and if anyone wants to comment, keep these things in mind.

mgold commented 7 years ago

Some prior art (note that many languages use ^ as bitwise xor, which will yield -4 in this particular case but not in general):

$ ghci
Prelude> -2**2
-4.0

$ python
>>> -2**2
-4

$ irb # Ruby
irb(main):001:0> -2**2
=> -4

# JavaScript does not have an exponentiation operation, so avoids the confusion
$node
> Math.pow(-2, 2)
4
> -Math.pow(2, 2)
-4

$ elm repl
> -2^2
4 : number

The proposal, then, is to treat -2^2 as -(2^2) instead of (-2)^2.

lydell commented 7 years ago

ES2016:

> 2 ** 2
4
> -2 ** 2
SyntaxError: unparenthesized unary expression can't appear on the left-hand side of '**'
> (-2) ** 2
4

(The above was done in the Firefox 52 web console.)

evancz commented 7 years ago

@mgold, can you say -2 ** 2 with spaces in Haskell, Python, and Ruby? In other words, do all these languages treat ** as part of a number literal, or is it an infix operator?

If it's an infix operator, this seems pretty weird to me. Like saying -2 + 3 should be -(2 + 3) and give back -5

For my future reference, here is a discussion of this question.

mgold commented 7 years ago

can you say -2 ** 2 with spaces in Haskell, Python, and Ruby?

In all three of those languages, the version with spaces behaves identically to the version without.

It seems to be an infix operator accepting arbitrary numeric expressions:

$ python
>>> (2*2) ** (1+1+1)
64

The Dr. Math article you linked to is pretty compelling. I'd be in favor of this change.

toonn commented 6 years ago

I'm surprised nothing has been done about this for so long. To further clarify: in haskell there are no negative number literals. Unary - is an operator and exponentiation is an operator that happens to take precedence over the former. I'd expect everyone to be familiar with this order of operations from maths classes.

The linked article does mention Excel uses the same (non-standard) precedence for unary minus. So there is non insignificant prior art but I hope the whole of mathematics and the majority of programming languages can convince the developers w.r.t. what the least surprising behavior is.

This backwards incompatible change is something that'll only become harder to implement the longer it remains neglected. That's why I decided to bump the issue because the current situation means people can't rely on the precedence of unary minus and therefore need lots of parentheses. Whether the issue is closed or resolved we will finally be able to rely on the precedence rather than parentheses.

Qqwy commented 5 years ago

I would also accept -2^2 raising a compile-error or warning asking people to clarify what they mean ((-2)^2 vs -(2^2), but this definitely should not implicitly have surprising behaviour.

drathier commented 5 years ago

I've since learned two definitions of unary negation: -x is defined as either 0 - x or -1 * x.

-2^2 := 0 - 2^2 or -1 * 2^2. Similarly, -2*2 := 0 - 2*2 or -1 * 2*2, and -2+2 := 0 - 2+2 or -1 * 2+2. It seems to work for more complex expressions too: -2*3+4 := 0 - 2*3+4 or -1 * 2*3+4 and even for anti-commutative operators: -2-3 := 0 - 2-3 or -1 * 2-3.

I can't think of an example where the two definitions would disagree. D-lang puts unary negation between exponentation and multiplication, and so does Python.

rlefevre commented 5 years ago

There are a lot of different implementations of exponentiation associativity and negation precedence.

From https://codeplea.com/exponentiation-associativity-options (with elm added):

Left or Right Associativity of the Exponentiation Operator?

What Code Tested Result Associativity
Elm 2^2^3 256 right-assiciative
Bash 2**2**3 256 right-associative
DuckDuckGo 2^2^3 256 right-associative
Excel 2^2^3 64 left-associative
EtherCalc 2^2^3 64 left-associative
Fortran 2**2**3 256 right-associative
Google 2^2^3 256 right-associative
Google Sheets 2^2^3 256 right-associative
Hand-held Calculators     Varies
Lua 2^2^3 256 right-associative
Matlab 2^2^3 64 left-associative
Octave 2^2^3 64 left-associative
Perl 2**2**3 256 right-associative
PostgreSQL 2^2^3 64 left-associative
Python 2**2**3 256 right-associative
Ruby 2**2**3 256 right-associative
Tcl 2**2**3 256 right-associative
WolframAlpha 2^2^3 256 right-associative

Variable Negation or Exponentiation First?

What Code Tested Result First Step
Elm -2^2 4 negation
Bash -2**2 4 negation
DuckDuckGo -2^2 4 negation
Excel -2^2 4 negation
EtherCalc -2^2 4 negation
Fortran -2**2 -4 exponentiation
Google -2^2 -4 exponentiation
Google Sheets -2^2 4 negation
Handheld Calculators -2^2   Varies
Lua -2^2 -4 exponentiation
Matlab -2^2 -4 exponentiation
Octave -2^2 -4 exponentiation
Perl -2**2 -4 exponentiation
PostgreSQL -2^2 4 negation
Python -2**2 -4 exponentiation
Ruby -2**2 -4 exponentiation
Tcl -2**2 4 negation
WolframAlpha -2^2 -4 exponentiation

Even different versions of TI handheld calculators may have different implementations.

AFAICT, Elm for now is right-associative for exponentiation, but put negation precedence over exponentiation, like bash, DuckDuckGo, Google sheets and Tcl.

I believe that no implementation is really wrong, because there is no standard, as programming languages (and text inputs) are not exactly mathematical notation.

That said, most likely because it is closer to mathematical notation, most recent implementations these days seem to use right associativity for exponentiation and higher precedence of exponentiation (not addition) over negation (Haskell, Wolfram, Python, Julia, ...). So this might be currently the most expected implementation and the one to favor for new languages (except those that purposely want to maintain compatibility with some legacy implementations like excel or matlab, or those that prefer to raise a warning because of the ambiguity).

drathier commented 5 years ago

Does that mean 4^2^-1 = 2? Should 2^-1^2 parse as 2^(-1)^2?

rlefevre commented 5 years ago

Most implementations that use right associativity for exponentiation and higher precedence of exponentiation over negation (tested on google, wolfram and python) would interpret those as:

Currently elm returns:

Victor-Savu commented 4 years ago

@rlefevre commented above

There are a lot of different implementations of exponentiation associativity and negation precedence.

From https://codeplea.com/exponentiation-associativity-options (with elm added):

I'm a bit confused about the table in that article. The operator is referred to as "the exponentiation operator" and there is an example for C++ where there is no exponentiation operator. ^. This is an exaple of what @mgold mentioned above.

If that is indeed the case, I don't believe it is fair to compare the precedence of the xor operation in C++ with the exponentiation operator in other languages.

As an aside, the caret (^) symbol is used a the exponentiation operator as an allusion to the superscript notation used when typesetting exponentiation: -3 ^ 2 = -32 = 9

rlefevre commented 4 years ago

@Victor-Savu Fair enough, but it is linked to the article following note:

Am I being disingenuous in the C++ example? I don't think so. C++ programmers write this stuff.

And there is a link to an example using ^ operator overloading: https://ideone.com/deqwdK.

But even if the specific case of C++ was wrong among tenths of other languages in the article, I don't think this changes anything to the discussion anyway, which is about Elm.

Thanks for the remark though, I will remove C++ from my copy/paste of the table to avoid confusion.