Functional Syntax?

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?

6 Likes

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.

2 Likes

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.

7 Likes

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.

1 Like

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

7 Likes

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.

2 Likes

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

6 Likes

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.

3 Likes

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

1 Like

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,

3 Likes

Actually, -(2.abs) is what I’d expect. Currently, it behaves as (-2).abs.

8 Likes

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.

3 Likes

-<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
5 Likes