Overloading + and + = operators for "room classes"
I want to create extension functions for classes that encapsulate simple Number
s. For example DoubleProperty
. I ran into the problem that I cannot overload the +
and operators at the same time +=
.
I don't want to create behavior that passes the following tests:
class DoublePropertyTest {
lateinit var doubleProperty: DoubleProperty
@Before
fun initialize() {
doubleProperty = SimpleDoubleProperty(0.1)
}
@Test
fun plus() {
val someProperty = doubleProperty + 1.5
assertEquals(someProperty.value, 1.6, 0.001)
}
@Test
fun plusAssign() {
val someProperty = doubleProperty
doubleProperty += 1.5 //error if + and += are overloaded
assert(someProperty === doubleProperty) //fails with only + overloaded
assertEquals(doubleProperty.value, 1.6, 0.001)
}
}
It can be implemented using these extension functions:
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
operator fun WritableDoubleValue.plusAssign(number: Number)
= set(get() + number.toDouble())
The problem is that if +
overlapped, +=
also cannot be overloaded:
Assignment operators ambiguity. All these functions match.
- public operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
- public operator fun WritableDoubleValue.plusAssign(number: Number): Unit
If I overload just the operator +
, the new DoubleProperty
object is returned +=
instead of the original one.
Is there a way to get around this limitation?
source to share
Strange operator +=
in Kotlin
you can overload the operator plus
and plusAssign
in kotlin, but you must follow the rules of Kotlin to solving the country's conflict +=
.
introduce an immutable class structure for the operator
plus
, which means that any class outside the class cannot edit its internal data.introduce a mutable class structure for the operator
plusAssign
, which means that its internal data can be edited anywhere.
Kotlin has already done things like this in stdlib
for classes Collection
and Map
, Collection # plus and MutableCollection # plusAssign , as shown below:
operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T>
// ^--- immutable structure
operator fun <T> MutableCollection<in T>.plusAssign(elements: Iterable<T>)
// ^--- mutable structure
But wait, how do we resolve the conflict when we use the operator +=
?
IF list is immutable Collection
, then you must define a mutable variable var
, then the operator is used plus
, since its internal state cannot be edited. eg:
// v--- define `list` with the immutable structure explicitly
var list: List<Int> = arrayListOf(1); //TODO: try change `var` to `val`
val addend = arrayListOf(2);
val snapshot = list;
list += addend;
// ^--- list = list.plus(addend);
// list = [1, 2], snapshot=[1], addend = [2]
IF list is mutable MutableCollection
, then you have to define an immutable variable val
, then the operator is used plusAssign
, since its internal state can be edited anywhere. eg:
// v--- `list` uses the mutable structure implicitly
val list = arrayListOf(1); //TODO: try change `val` to `var`
val addend = arrayListOf(2);
val snapshot = list;
list += addend;
// ^--- list.plusAssign(addend);
// list = [1, 2], snapshot=[1, 2], addend = [2]
On the other hand, you can overload the operator with diff signatures, each signature for a different context , and kotlin does this as well, for example: Collection # plus . eg:
var list = listOf<Int>();
list += 1; //list = [1];
// ^--- list = list.plus(Integer);
list += [2,3]; //list = [1, 2, 3]
// ^--- list = list.plus(Iterable);
source to share
There are two problems with your implementation of operator override:
1.inconsistent type after plus
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
Any instance ObservableDoubleValue
plus a Number
, got an instance DoubleProperty
(or say instance SimpleDoubleProperty
). Let's say I have a type that ComplexDoubleProperty
implements ObservableDoubleValue
, you will see:
var a = getComplexDoubleProperty()
a = a + 0.1 //compile error, WTF?
//or even
var b = SimpleDoubleProperty(0.1)
b = b + 0.1 //compile error, because b+0.1 is DoubleProperty
You can see that this behavior is meaningless.
2.a = a + b and a + = b must be identical
If your implementation compiles you will have
var a: DoubleProperty = SimpleDoubleProperty(0.1) //add DoubleProperty to make it compile
var b = a
a += 0.1
println(b == a)
outputs true
because it +=
sets the value to the original instance. If you replace a+=0.1
with a=a+0.1
, you get false
because a new instance is returned. Generally speaking, a=a+b
and a+=b
not identical in this implementation.
To fix the two problems above, my suggestion is
operator fun SimpleDoubleProperty.plus(number: Number): SimpleDoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
so you don't need to override plusAssign
. The solution is not as general as yours, but it is correct if you only have computations SimpleDoubleProperty
, and I assume you do this because in your implementation it plus
always returns an instance SimpleDoubleProperty
.
source to share
You can not overload the like +
, as well +=
. Reload one of them.
When you write + = in your code, in theory, plus plus functions plusAssign (see Figure 7.2). If so, and both functions are defined and applicable, the compiler reports an error.
I copied / pasted from the book Kotlin in Action !
source to share