Трейт Scala и параметризация его методов

В своем приложении я хочу использовать такую ​​черту:

trait HasBuffer[+A] {

    var items = Buffer[A]()

    def add[A](item: A) { items += item }
    def remove[A](item: A) { items -= item }
    def set(is: Buffer[A]) { items = is }
    def clear { items clear }
}

Классы, наследующие эту черту, должны иметь возможность буферизовать любые экземпляры классов, которые являются дочерними элементами класса A. Однако как в методах добавления, так и в методах удаления компилятор жалуется на добавление или удаление элемента из элементов, которые «несоответствие типов; найдено: item.type (с базовым типом A) требуется: A". Как я должен это понимать? В чем здесь моя ошибка и что делать?


person noncom    schedule 03.02.2012    source источник


Ответы (2)


Вы параметризуете методы другим параметром типа A, который отличается от параметра вашего определения класса. Вот версия, которую вы написали с переименованными параметрами:

trait HasBuffer[+A] {

    var items = Buffer[A]()

    def add[B](item: B) = items += item
    def remove[B](item: B) { items -= item }
    def set(is: Buffer[A]) { items = is }
    def clear { items clear }
}

Теперь должно быть понятно, почему компилятор это отвергает.

Вместо этого вы можете просто написать такие методы:

def add(item: A) = items += item
def remove(item: A) { items -= item }

Однако тогда вы все равно будете получать ошибки компилятора о том, что ковариантный тип A встречается в противоположных и инвариантных позициях.

Суть ковариантности в том, что если вы ожидаете HasBuffer[AnyVal], можно перейти в HasBuffer[Int]. Однако, если вы ожидаете AnyVal и используете этот тип также для метода add, вы сможете добавить совершенно другой тип к вашему HasBuffer[Int]. Следовательно, компилятор отвергает это.

Вместо этого вам нужно будет указать нижнюю границу параметра типа следующим образом:

trait HasBuffer[+A, V <: A] {

    var items  = Buffer[V]()

    def add(item: V) = items += item
    def remove(item: V) { items -= item }
    def set(is: Buffer[V]) { items = is }
    def clear { items clear }
}

Теперь у вас могут быть такие методы, как следующие:

def doSomething[X <: AnyVal](b : HasBuffer[AnyVal, X], e : X) = b.add(e)

Этот метод будет работать со всеми видами комбинаций параметров типа HasBuffer, которые удовлетворяют требуемой нижней границе.

Мысленно сравните это с идеей об отсутствии нижней границы. Тогда метод стал бы примерно таким:

// doesn't make much sense!
def doSomething(b : HasBuffer[AnyVal], e : AnyVal) = b.add(e)

Если вы вызовете это с объектом типа HasBuffer[Int] и Double, все будет хорошо. Вы, вероятно, не будете счастливы позже, когда обнаружите, что ваш буфер, который должен содержать только Ints, теперь содержит Double.

person Frank    schedule 03.02.2012
comment
Да, я должен признать, что это объяснение очень подробное и, таким образом, может служить руководством для тех, кто может столкнуться с подобной проблемой. Что касается компилятора Scala - очень жаль, что он допускает такие апараметры типов с эквивалентными именами. Логично было бы запретить их совсем. Так как никто не захочет называть параметры своего типа подобным образом, а те, кто захочет, просто провозгласят плохой стиль кодирования. Обфускация кода — это дополнительная область, она не должна появляться в простом программировании. - person noncom; 03.02.2012

Проблема в том, что вы определили методы добавления и удаления по отношению к другому параметру типа A, который, хотя и имеет такое же имя, на самом деле является параметром нового типа.

Это должно вас заинтересовать:

def add(item: A)
def remove(item: A)

РЕДАКТИРОВАТЬ: Фрэнк прав, я забыл иметь дело с тем фактом, что Buffer является ковариантным в A, где в исходном объявлении A явно находится в контравариантном положении. Следовательно, вышеизложенное является лишь частичным решением проблемы ОП.

person fotNelton    schedule 03.02.2012
comment
-1. даже несмотря на то, что ОП принял это, это не решает проблему использования ковариантного типа в контравариантной позиции. Или проще говоря: он не будет компилироваться только с этим изменением. - person Frank; 03.02.2012