Почему пример не компилируется, иначе как (со-, противо- и не-) дисперсия работает?

Есть ли лучший способ добиться аналогичного удобства?

blockquote>

Я не знаю, если это обязательно лучше, но вы можете это сделать:

class Foo(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


>>> foo = Foo(a = 1, b = 'bar', c = [1, 2])
>>> foo.a
1
>>> foo.b
'bar'
>>> foo.c
[1, 2]
>>> 

Предоставлено Python Питера Норвига : Редко ответы на вопросы .

146
задан Community 23 May 2017 в 11:47
поделиться

3 ответа

В общем ковариантный параметр типа является тем, которому позволяют варьироваться вниз, поскольку класс с выделенными подтипами (альтернативно, меняйтесь в зависимости от выделения подтипов, следовательно "co -" префикс). Более конкретно:

trait List[+A]

List[Int] подтип List[AnyVal] потому что Int подтип AnyVal. Это означает, что можно обеспечить экземпляр List[Int] когда значение типа List[AnyVal] ожидается. Это - действительно очень интуитивный путь к дженерикам для работы, но оказывается, что это необоснованно (повреждает систему типов) при использовании в присутствии изменяемых данных. Поэтому дженерики являются инвариантными в Java. Краткий пример необоснованных массивов Java использования (которые являются ошибочно ковариантными):

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Мы просто присвоили значение типа String к массиву типа Integer[]. По причинам, которые должны быть очевидными, это - плохие новости. Система типов Java на самом деле позволяет это во время компиляции. JVM "услужливо" бросит ArrayStoreException во времени выполнения. Система типов Scala предотвращает эту проблему потому что параметр типа на Array класс является инвариантным (объявление [A] вместо [+A]).

Обратите внимание, что существует другой тип различия, известного как контравариантность. Это очень важно, поскольку это объясняет, почему ковариантность может вызвать некоторые проблемы. Контравариантность является буквально противоположностью ковариантности: параметры варьируются вверх с выделением подтипов. Это намного менее распространено частично, потому что это таким образом парадоксально, хотя это действительно имеет одно очень важное приложение: функции.

trait Function1[-P, +R] {
  def apply(p: P): R
}

Заметьте "-" аннотация различия на P введите параметр. Это объявление в целом означает это Function1 контравариант в P и ковариантный в R. Таким образом мы можем получить следующие аксиомы:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

Заметьте это T1' должен быть подтип (или тот же тип) T1, тогда как это - противоположное для T2 и T2'. На английском языке это может быть считано как следующее:

Функция A является подтипом другой функции B, если тип параметра A является супертипом типа параметра B, в то время как тип возврата A является подтипом типа возврата B.

Причину этого правила оставляют как осуществление читателю (подсказка: думайте о различных случаях, поскольку функции с выделенными подтипами, как мой пример массива сверху).

С Вашим новооткрытым знанием co - и контравариантности, необходимо смочь видеть, почему следующий пример не скомпилирует:

trait List[+A] {
  def cons(hd: A): List[A]
}

Проблема - это A является ковариантным, в то время как cons функция ожидает, что ее параметр типа будет инвариантным. Таким образом, A варьируется неправильное направление. Интересно достаточно мы могли решить эту проблему путем создания List контравариант в A, но затем тип возврата List[A] было бы недопустимо как cons функция ожидает, что ее тип возврата будет ковариантным.

Наши только две опции здесь к a), делают A инвариант, утрачивая хорошие, интуитивные имущества выделения подтипов ковариантности или b) добавляет локальный параметр типа к cons метод, который определяет A как нижняя граница:

def cons[B >: A](v: B): List[B]

Это теперь допустимо. Можно вообразить это A варьируется вниз, но B может варьироваться вверх относительно A с тех пор A его нижняя граница. С этим объявлением метода мы можем иметь A будьте ковариантными, и все удается.

Заметьте, что этот прием только работает, если мы возвращаем экземпляр List который специализирован на меньшем-количестве-определенном-типе B. При попытке сделать List изменяемый, вещи ломаются, так как Вы заканчиваете тем, что пытались присвоить значения типа B к переменной типа A, который запрещен компилятором. Каждый раз, когда у Вас есть переменчивость, у Вас должен быть какой-то мутатор, который требует параметра метода определенного типа, который (вместе со средством доступа) подразумевает инвариантность. Работы ковариантности с неизменными данными начиная с единственной возможной операции являются средством доступа, которому можно дать ковариантный тип возврата.

299
ответ дан 23 November 2019 в 22:42
поделиться

Необходимо применить нижнюю границу на параметр. Мне нелегко помнить синтаксис, но я думаю, что он выглядел бы примерно так:

class Slot[+T, V <: T](var some: V) {
  //blah
}

Scala-by-example немного трудно понять, несколько конкретных примеров помогли бы.

3
ответ дан oxbow_lakes 23 May 2017 в 21:47
поделиться

См. Scala примером , страница 57 + для полного обсуждения этого.

, Если я понимаю Ваш комментарий правильно, необходимо перечитать проход, запускающийся внизу страницы 56 (в основном, что я думаю, что Вы просите, не безопасно с точки зрения типов без проверок времени выполнения, которые не делает scala, таким образом, Вам не повезло). Перевод их примера для использования конструкции:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

, Если Вы чувствуете, что я не понимаю Вашего (вполне возможного) вопроса, попытайтесь добавить больше объяснения / контекст к описанию проблемы, и я попробую еще раз.

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

7
ответ дан MarkusQ 23 May 2017 в 21:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: