Исходя из этого вопроса , может кто-нибудь объяснить в Scala следующее:
class Slot[+T] (var some: T) {
// DOES NOT COMPILE
// "COVARIANT parameter in CONTRAVARIANT position"
}
Я понимаю различие между +T
и T
в объявлении типа (оно компилируется, если я использую T
). Но тогда как на самом деле написать класс, который является ковариантным по параметру типа, не прибегая к созданию непараметризованной вещи ? Как я могу гарантировать, что следующее может быть создано только с экземпляром T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
РЕДАКТИРОВАТЬ - теперь получил это до следующего:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
это все хорошо, но теперь у меня есть два параметра типа, где мне нужен только один. Я снова задам вопрос так:
Как я могу написать неизменный Slot
класс, который является ковариантным в своем типе?
РЕДАКТИРОВАТЬ 2 : Дух! Я использовал var
и нет val
. Вот что я хотел:
class Slot[+T] (val some: T) {
}
generics
scala
covariance
contravariance
oxbow_lakes
источник
источник
var
что устанавливается, аval
нет. Это та же самая причина, по которой неизменные коллекции scala являются ковариантными, а изменяемые - нет.Ответы:
Как правило, параметр ковариантного типа - это параметр, который может изменяться в зависимости от подтипа класса (альтернативно, варьироваться в зависимости от подтипа, отсюда и префикс «co»). Более конкретно:
List[Int]
является подтипом,List[AnyVal]
потому чтоInt
является подтипомAnyVal
. Это означает, что вы можете предоставить случай,List[Int]
когдаList[AnyVal]
ожидается значение типа . Это действительно очень интуитивный способ работы генериков, но оказывается, что он неэффективен (нарушает систему типов) при использовании в присутствии изменяемых данных. Вот почему дженерики инвариантны в Java. Краткий пример несостоятельности с использованием массивов Java (которые ошибочно ковариантны):Мы просто присвоили значение типа
String
массиву типовInteger[]
. По причинам, которые должны быть очевидны, это плохие новости. Система типов Java фактически позволяет это во время компиляции. JVM «услужливо» сгенерируетArrayStoreException
во время выполнения. Система типов Scala предотвращает эту проблему, потому что параметр типа вArray
классе является инвариантным ([A]
вместо объявления[+A]
).Обратите внимание, что существует другой тип дисперсии, известный как контравариантность . Это очень важно, поскольку объясняет, почему ковариация может вызвать некоторые проблемы. Контрвариация буквально противоположность ковариации: параметры изменяются вверх с подтипами. Это гораздо реже, отчасти потому, что оно настолько нелогично, хотя у него есть одно очень важное приложение: функции.
Обратите внимание на аннотацию " - " для
P
параметра типа. Это объявление в целом означает, чтоFunction1
является контравариантным вP
и ковариантным вR
. Таким образом, мы можем вывести следующие аксиомы:Обратите внимание, что это
T1'
должен быть подтип (или тот же тип)T1
, тогда как дляT2
иT2'
. На английском языке это можно прочитать следующим образом:Причина этого правила оставлена читателю в качестве упражнения (подсказка: подумайте о разных случаях, когда функции имеют подтипы, как в примере с моим массивом выше).
С вашими новыми знаниями о со-и контравариантности вы сможете понять, почему следующий пример не скомпилируется:
Проблема в том, что
A
она ковариантна, тогда какcons
функция ожидает, что ее параметр типа будет инвариантным . Таким образом,A
меняется неправильное направление. Интересно, что мы могли бы решить эту проблему, сделав ееList
контравариантнойA
, но тогда возвращаемый типList[A]
был бы недействительным, посколькуcons
функция ожидает, что ее возвращаемый тип будет ковариантным .Здесь есть только два варианта: а) сделать
A
инвариант, потеряв приятные, интуитивно понятные свойства ковариации подтипирования, или б) добавить параметр локального типа вcons
метод, который определяетA
как нижнюю границу:Теперь это действительно. Вы можете себе представить , что
A
в той или иной вниз, ноB
может изменяться вверх относительно ,A
так какA
это его нижняя граница. С этим объявлением метода мы можемA
быть ковариантными, и все работает.Обратите внимание, что этот прием работает только в том случае, если мы возвращаем экземпляр
List
, специализирующийся на менее конкретном типеB
. Если вы попытаетесь сделатьList
изменчивым, все пойдет не так, как вы пытаетесь присвоить значения типаB
переменной типаA
, что запрещено компилятором. Всякий раз, когда у вас есть изменчивость, вам нужен какой-то мутатор, для которого требуется параметр метода определенного типа, который (вместе с аксессором) подразумевает инвариантность. Covariance работает с неизменяемыми данными, поскольку единственной возможной операцией является метод доступа, которому может быть задан ковариантный тип возврата.источник
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
иdef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. Тогда,iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
все в порядке, поскольку наш пастух может пасти коров, ноiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
дает ошибку компиляции, так как наш пастух не может пасти всех животных.@Daniel объяснил это очень хорошо. Но объясним это вкратце, если бы это было разрешено:
slot.get
затем выдаст ошибку во время выполнения, так как она была неудачной при преобразованииAnimal
вDog
(duh!).В целом изменчивость не сочетается с ковариацией и противоречивостью. Вот почему все коллекции Java инвариантны.
источник
См. Scala на примере , стр. 57+ для полного обсуждения этого.
Если я правильно понимаю ваш комментарий, вам нужно перечитать отрывок, начинающийся внизу страницы 56 (в основном, то, что я думаю, что вы просите, не безопасно для типов без проверок во время выполнения, чего не делает scala, так что тебе не повезло). Переводим их пример для использования вашей конструкции:
Если вы чувствуете, что я не понимаю ваш вопрос (вполне вероятная возможность), попробуйте добавить дополнительные объяснения / контекст к описанию проблемы, и я попробую еще раз.
В ответ на ваше редактирование: Неизменные слоты - это совсем другая ситуация ... * smile * Я надеюсь, что приведенный выше пример помог.
источник
Вам необходимо применить нижнюю границу к параметру. Мне трудно вспомнить синтаксис, но я думаю, что это будет выглядеть примерно так:
Сложно понять Scala-by-example, несколько конкретных примеров помогло бы.
источник