Different operators have different priorities (like * and +), and an infix function also has a priority which is different from that of the other operators, so there is no requirement for consistency here.
Yes, of course, this is one of any number of tricky details that a programmer could get wrong if she isn’t paying attention, but as @yole has already mentioned, this is the correct and intended behavior, and that behavior (the low priority of unary operators) is not something unique to Kotlin; it’s standard across programming languages.
Your particular example might make it feel like the behavior is wrong, but imagine this contrasting example:
data class
Person( var age: BigDecimal )
{
fun getAge(): BigDecimal = age
}
val person = Person( BigDecimal( 42 ) )
val x = -person.getAge()
Do you expect x to equal a BigDecimal instance of -42? Or do you expect a compile-time error that the class Person doesn’t have a unaryMinus operator?
To at least the benefit of consistency and simplicity-of-implementation, just like the D programming language does, Kotlin’s lexer & parser is designed to treat -1 in the same manner as it treats -person: as the unary minus operator applied to the integer literal 1, not as some unique and different symbol from 1 (just like -person isn’t some unique symbol that’s unrelated to person). And the priority of unary operators, additionally, makes it such that -person.getAge() applies the unary operator to person.getAge() rather than just to person (just as it applies the unary operator to 1.plus(2) rather than to just 1).
I’ve tried Swift, C# and Ruby in the past few days, OK, only in Ruby -1.plus(2) is 1.
So I don’t think it’s a problem for the Kotlin’s lexer & parser to treat -1 in the different manner as it treats -person. An small if will simply solve this problem.
It is designed to be so… I don’t think it’s a good idea due to the principle of least astonishment.
I still think it would be better if it is not designed like this. It would be more consistent compare to the infix function and would be more comfortable for most programmers who meet it.
It has already been designed like this, and changing the design would be a backwards incompatible change. And once again: the unary minus operator has different precedence from an infix call, and there is no more reason to complain about the inconsistency than to complain about the inconsistency of handling + and * signs in mathematical expressions.
If I were reading somebody else’s code and saw -1.plus(2) I would first ask, why didn’t they just write -1 + 2? And without a defense of the strange looking code I personally would first guess that -1 was a constant, not a unary minus operator.
In the spirit of least astonishment, I asked a couple non-kotlin developers what they guessed -1.plus(2) should be, and I get both answers -3 and 1. So I guess write your expression as -1 + 2 if you don’t want to confuse people.
Q: “Hey guys, guess what -1.plus(2) will be in Kotlin?”
A: “(Since you asked me deliberately, perhaps it’s not 1) Um… -3?”
It’s different when you read about it.
“Why does these tests got failed !
Failed at -2.next() shouldBe -1?!”
I wrote extension functions for Int and Float. When I wrote some tests, I found this problem. -1.plus(2) is just a simple example to emphasize how funny this problem is.
I wrote something like
-1.atLeast(0) shouldBe 0 //equals to Math.max(num,atLeast)
or some BDD assertions:
-1.shouldSmallerThan(0)
But when take these functions in the project it will be vocabulary.atLeast(0). No confusion at all !
Yole said what is done cannot be undone, so I’m not confused about it at all now.
Finally I realize there’s no perfect languages, it’s my fault to always think that the only constant in the world is change.
I still use Kotlin everyday and discover a lot of fun about Kotlin,
Happy !