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?

+3


source to share


4 answers


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);

      

+2


source


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

.

+2


source


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 !

0


source


If DoubleProperty

is your class, you can make it plus

and plusAssign

its methods, which should clear up any ambiguity.

-2


source







All Articles