Перегрузка операторов + и += для "числовых классов"

Я хочу создать функции расширения для классов, которые инкапсулируют простые Numbers. Например DoubleProperty, Я столкнулся с проблемой, что я не могу перегрузить + и += оператор одновременно.

Я не хочу создавать поведение, которое проходит следующие тесты:

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

Это может быть реализовано с использованием этих функций расширения:

operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty 
    = SimpleDoubleProperty(get() + number.toDouble())

operator fun WritableDoubleValue.plusAssign(number: Number) 
    = set(get() + number.toDouble())

Проблема в том, что если + перегружен += не может быть перегружен, а также:

Assignment operators ambiguity. All these functions match.
- public operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
- public operator fun WritableDoubleValue.plusAssign(number: Number): Unit

Если бы я только перегрузить + оператор, новый DoubleProperty объект возвращается += операции вместо начальной.

Есть ли способ обойти это ограничение?

4 ответа

Решение

Странный += оператор в Котлине

вы можете перегружать plus оператор и plusAssign оператор в kotlin, но вы должны следовать правилам kotlin для решения странных += конфликты.

  1. ввести неизменную структуру класса для plus оператор, который означает, что любой класс вне класса не может редактировать свои внутренние данные.

  2. ввести изменяемую структуру класса для plusAssign оператор, который означает, что его внутренние данные могут быть изменены в любом месте.

Котлин уже сделал такие вещи в stdlib для Collection & Map классы, Collection # plus и MutableCollection # plusAssign, как показано ниже:

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

Но подождите, как решить конфликт, когда мы используем += оператор?

ЕСЛИ список неизменен Collection тогда вы должны определить изменчивый var переменная, то plus оператор используется, так как его внутреннее состояние не может быть отредактировано. например:

//         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]

Если список изменчив MutableCollection тогда вы должны определить неизменный val переменная, то plusAssign оператор используется, поскольку его внутреннее состояние можно редактировать где угодно. например:

//    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]

С другой стороны, вы можете перегружать оператора с помощью подписей diff, каждой подписи для своего контекста, и kotlin также делает это, например: Collection # plus. например:

var list = listOf<Int>();

list += 1; //list = [1];
//   ^--- list = list.plus(Integer);

list += [2,3]; //list = [1, 2, 3]
//   ^--- list = list.plus(Iterable);

У вашей реализации переопределения оператора есть две проблемы:

1. противоречивый тип послеplus

operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty 
    = SimpleDoubleProperty(get() + number.toDouble())

любой ObservableDoubleValue экземпляр плюс Number, Получил DoubleProperty экземпляр (или, скажем, SimpleDoubleProperty пример). Допустим, у меня есть тип ComplexDoubleProperty инвентарь ObservableDoubleValue, ты увидишь:

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

Вы можете видеть, что это поведение не имеет смысла.

2. a = a + b и a+=b должны быть идентичны

Если ваша реализация компилируется, у вас будет

var a: DoubleProperty = SimpleDoubleProperty(0.1)  //add DoubleProperty to make it compile
var b = a
a += 0.1
println(b == a)

печать true так как += устанавливает значение в исходный экземпляр. Если вы замените a+=0.1 с a=a+0.1 ты получишь false потому что новый экземпляр возвращается. Вообще говоря, a=a+b а также a+=b не идентичны в этой реализации.

Чтобы исправить две проблемы выше, я предлагаю

operator fun SimpleDoubleProperty.plus(number: Number): SimpleDoubleProperty
        = SimpleDoubleProperty(get() + number.toDouble())

так что вам не нужно переопределять plusAssign, Решение не такое общее, как у вас, но оно правильное, если у вас есть только SimpleDoubleProperty расчеты, и я верю, что вы делаете, потому что в вашей реализации, plus всегда возвращает SimpleDoubleProperty пример.

Вы не можете перегружать оба + а также +=, Перегрузите один из них.

Когда вы пишете += в своем коде, теоретически могут вызываться обе функции плюс функции PlusAssign (см. Рисунок 7.2). Если это так, и обе функции определены и применимы, компилятор сообщает об ошибке.

Я скопировал / вставил из Kotlin в книгу действий!

Если DoubleProperty это ваш класс, вы можете сделать plus а также plusAssign его методы, которые должны разрешить любую неопределенность.

Другие вопросы по тегам