Open drathier opened 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.
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
.
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.)
@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.
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.
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.
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.
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.
There are a lot of different implementations of exponentiation associativity and negation precedence.
From https://codeplea.com/exponentiation-associativity-options (with elm added):
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 |
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 |
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 |
-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).
Does that mean 4^2^-1 = 2
? Should 2^-1^2
parse as 2^(-1)^2
?
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:
-2^2^3 = -(2^(2^3)) = -256
4^2^-1 = 4^(2^(-1)) = 2
2^-1^2 = 2^(-(1^2)) = 0.5
Currently elm returns:
-2^2^3
= 2564^2^-1
= error (unknown operator)2^-1^2
= error (unknown operator)@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
@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.
-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/evenn
, 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.