Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 11.0.11).
Type in expressions for evaluation. Or try :help.
scala> val x = 2
val x: Int = 2
scala> -x.abs
val res0: Int = -2
scala> - 2.abs
val res1: Int = 2
Are people cool with this?
Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 11.0.11).
Type in expressions for evaluation. Or try :help.
scala> val x = 2
val x: Int = 2
scala> -x.abs
val res0: Int = -2
scala> - 2.abs
val res1: Int = 2
Are people cool with this?
What happens with -(2).abs
? It’s nice for me if that returns -2
, since spaces between -
and the number are sometimes useful
Not cool at all. The behavior is the same in Scala 3.
Because of little edge cases like this in which I am unwilling to hold in my head/memory across many code-bases, my default is to add (unnecessary but) clarifying parenthesis everywhere.
And without deep researching it, I suspect that it isn’t possible to eliminate every single edge case due to the language feature interactions at this compressed of a point in the code. IOW, there is a tension between the reading simplification versus the explicit meaning expressibility.
The -x.abs
syntax is actually not functional but object-oriented. Functional syntax would be instead:
import Math.abs
abs(-x)
, and the ambiguity vanishes.
Then why not abs(neg(x))
, since unary_-
is a method on x
?
I’m pretty sure this post means functional as in the usage sense, not as in the FP sense
Referential transparency.
But then, would -someVariable.abs
be abs(neg(someVariable))
or neg(abs(someVariable))
if someVariable
is not a literal but another kind of expression? I much prefer the latter. If -x.foo
was foo(neg(x))
, then !x.foo
should also be foo(not(x))
. That’d be very confusing.
We could be like Enso and use spaces for precedence…
Why? It makes more sense to me, unary operators should have the highest precedence. You could argue that abs(-x)
makes no sense, but that’s not up to the compiler to know
In my opinion behavior should be consistent and -2.abs
should be treated in the same ways as any other unary operation, e.g. ~2.abs
== ~ 2.abs
== val x = 2; ~x.abs
== ~(x.abs)
have the same result
Because .
has had higher precedence than unary operators for a while now, and changing it would likely break existing code, and also confuse people for whom the new syntax doesn’t make sense.
Agreed. Until it was mentioned, I would never have suspected that -2.abs
was 2
.
So (-2) should not parse as a single number literal but as 2.unary_- ?
This is probably main issue, -2
is native literal in many languages, so parser has special case for that and it cannot take into account context whether after literal there is a function or not
I agree it’s confusing. It demonstrates a particular trickiness at the interface between parser and lexer. Parser would like to consume a token stream. “-2
” must be two separate tokens “-
” and “2
” since otherwise x-2
would not parse correctly. But then, theoretically a space between -
and 2
should not matter since the token stream is not whitespace sensitive. So by the rules of the syntax,
-2.abs
is -(2.abs)
, which is not what’s expected intuitively. The way out of this is messy. We need to special treat -2
as a literal if a literal is expected in the parser and there’s no space between the sign and the first digit. That’s what’s implemented in Fix negative literals by odersky · Pull Request #12459 · lampepfl/dotty · GitHub,
Actually, -(2.abs)
is what I’d expect. Currently, it behaves as (-2).abs
.
One would expect -2
to be a single literal, not 2.unary_-
But then, this leaves the problems that { val x = 2; -x.abs }
and -2.abs
are not the same value.
The initial question was whether people are comfortable with that. Maybe some are, and some are not. Without trying, I would have guessed -2.abs
to be -2
, but maybe I’m in the minority.
-<number>
must be a literal since otherwise the minimal integer would not parse.
scala> 2147483648
1 |2147483648
|^^^^^^^^^^
|number too large
scala> -2147483648
val res1: Int = -2147483648