Scala - ковариантный тип в изменяемых коллекциях

Я новичок в мире Scala, и теперь я читаю книгу под названием «Scala in Action» (Ниланджан Райчаудхури), а именно часть под названием «Изменяемый объект должен быть инвариантным» на странице 97, и я не понимаю следующую часть, которая взято прямо из упомянутой книги.


Предположим, что ListBuffer ковариантен, и следующий фрагмент кода работает без каких-либо проблем с компиляцией:

scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)

Вы можете определить проблему? Поскольку все имеет тип Any, вы можете сохранить целочисленное значение в коллекции строк. Это катастрофа ждет своего часа. Чтобы избежать подобных проблем, всегда рекомендуется делать изменяемые объекты неизменными.


У меня были бы следующие вопросы ..

1) Какой тип everything на самом деле? String или Any? Объявление - "val everything: ListBuffer[Any]", и, следовательно, я ожидал Any, и поскольку все должно быть типа Any, я не вижу никаких проблем, чтобы иметь Integer и String в одном ListBuffer[Any]. Как я могу сохранить целочисленное значение в коллекции строк, как они пишут ??? Почему катастрофа ??? Почему я должен использовать List (который является неизменным) вместо ListBuffer (который является изменяемым)? Не вижу разницы. Я нашел много ответов о том, что изменяемые коллекции должны иметь инвариантный тип, а неизменные коллекции должны иметь ковариантный тип, но почему?

2) Что означает последняя часть «res4: everything.type = ListBuffer(1, pants)»? Что означает "everything.type"? Я предполагаю, что everything не имеет метода / функции или переменной с именем type .. Почему нет ListBuffer [Any] или ListBuffer [String]?

Большое спасибо,

Эндрю


person Andrew_256    schedule 21.03.2018    source источник
comment
Привет! Если у меня будет время позже, я могу попытаться написать полный ответ (если только кто-то не опередит меня или не закроет вопрос как дублирующий). Но пока что, похоже, эта статья может помочь вы с некоторыми понятиями.   -  person slouc    schedule 21.03.2018


Ответы (2)


1 Это не похоже на один вопрос, поэтому я должен разделить его дальше:

  1. «На самом деле» everything - это ListBuffer[_] со стертым типом параметра. В зависимости от JVM он содержит 32- или 64-битные ссылки на некоторые объекты. Типы ListBuffer[String] и ListBuffer[Any] - это то, что компилятор знает о них во время компиляции. Если он «знает» две противоречивые вещи, то это, очевидно, очень плохо.
  2. «Я не вижу проблем, чтобы иметь Integer и String в одном ListBuffer [Any]». Нет проблем с Int и String в ListBuffer[Any], потому что ListBuffer инвариантен. Однако в вашем гипотетическом примере ListBuffer ковариантен, поэтому вы сохраняете Int в ListBuffer[String]. Если кто-то позже получит Int от ListBuffer[String] и попытается интерпретировать его как String, то это, очевидно, очень плохо.

  3. «Как я могу сохранить целочисленное значение в коллекции строк в том виде, в каком они пишутся?» Зачем вам делать что-то, что явно очень плохо, как описано выше?

  4. «Почему катастрофа ???» Это не будет серьезной катастрофой. Java всегда жила с ковариантными массивами. Это не приводит к катаклизмам, это просто плохо и раздражает.

  5. «Почему я должен использовать List (который является неизменным) вместо ListBuffer (который является изменяемым)?» Нет абсолютного императива, который велит вам всегда использовать List и никогда не использовать ListBuffer. Используйте оба, когда это уместно. В 99,999% случаев List, конечно, более подходит, потому что вы используете Lists для представления данных чаще, чем вы разрабатываете сложные алгоритмы, требующие локального изменяемого состояния ListBuffer.

  6. «Я нашел много ответов, что изменчивые коллекции должны иметь инвариантный тип, а неизменные коллекции должны иметь ковариантный тип, но почему?». Это неправильно, вы слишком упрощаете. Например, интенсиональные неизменяемые множества не должны быть ни ковариантными, ни инвариантными, а контравариантными. Вы должны использовать ковариацию, контравариантность и инвариантность, когда это уместно. Эта маленькая глупая иллюстрация оказалась неоправданно эффективной для объяснения разницы, может быть, вы тоже сочтете ее полезной.

2 Это одноэлементный тип, как в следующем примере:

scala> val x = "hello"
x: String = hello

scala> val y: x.type = x
y: x.type = hello

Вот более подробное обсуждение мотивации этого.

person Andrey Tyukin    schedule 21.03.2018

Я согласен с большей частью того, что говорит @Andrey, я бы просто добавил, что ковариация и контравариантность принадлежат исключительно неизменяемым структурам, упражнение, предлагаемое в книгах, является всего лишь примером, чтобы люди могли понять, но невозможно реализовать изменяемую структуру, которая является ковариантный, вы не сможете его скомпилировать. В качестве упражнения вы можете попробовать реализовать MutableList[+A], вы обнаружите, что это невозможно сделать, не обманывая компилятор, помещая asInstanceOf везде

person Mikel San Vicente    schedule 21.03.2018
comment
Сказать, что невозможно реализовать изменяемую структуру, которая является ковариантной, снова является чрезмерным упрощением. Считается ли, например, изменяемый кортеж, который может содержать два значения a1 и a2 одного типа A и имеет метод изменения swap(): Unit как изменяемую структуру? Это, безусловно, структура данных (не очень полезная, но все же), и она определенно изменяема (хотя в ней есть только swap, без сеттеров). Не было бы проблем реализовать его как ковариантный. Вся эта неизменная = ковариантная изменчивая = инвариантная история - не правило, это в лучшем случае расплывчатая эвристика ... - person Andrey Tyukin; 21.03.2018
comment
Но я должен согласиться: просто попробуйте реализовать это, вы увидите, что вы вынуждены ставить asInstanceOf на все, в целом хороший намек. Этого нельзя понять, пока не попытаешься восстановить небольшую коллекцию игрушек самостоятельно. - person Andrey Tyukin; 21.03.2018
comment
Я не думаю, что изменяемый кортеж может быть реализован как ковариантный, если вы знаете, как это сделать, мне было бы очень интересно увидеть это - person Mikel San Vicente; 21.03.2018
comment
конечно, использование asInstanceOf неприемлемо для действительной реализации - person Mikel San Vicente; 21.03.2018
comment
Отступ, вероятно, сломается, но вот и все: class T[+A](a1: A, a2: A) { private[this] var v1: A = a1 private[this] var v2: A = a2 def _1: A = v1 def _2: A = v2 def swap(): Unit = { val temp = v2 v2 = v1 v1 = temp } } val t1: T[String] = new T("hello", "world") val t2: T[Any] = t1 // is ok, covariant println(t2._1) t1.swap() println(t2._1) // obviously mutable Единственное, на что можно возразить, это то, что изменяемый - это отвлекающий маневр и используется в смысле" не неизменный ". - person Andrey Tyukin; 21.03.2018
comment
ну, вы не реализовали никакую ковариантную операцию ... вот где у вас будут проблемы, но если речь идет только о T [A] ‹: T [Any], то это правда, что это можно сделать, но нет таких функций, как установка _2 может быть реализовано - person Mikel San Vicente; 21.03.2018
comment
но я понимаю вашу точку зрения, что технически возможно для очень ограниченных структур - person Mikel San Vicente; 21.03.2018
comment
Очень ограниченный - трудно определить количественно. Моя точка зрения скорее такова: ковариантность / инвариантность более или менее ортогональна любому другому свойству, например изменчивости / неизменности, способности к росту или чему-то еще. Одно из свойств не подразумевает другого. Например. неизменяемость и ковариация, будучи положительно коррелированными в большинстве библиотек, не подразумевают друг друга, всегда можно придумать несколько надуманный, но действительный контрпример. - person Andrey Tyukin; 21.03.2018
comment
Я понимаю, моя точка зрения заключалась в том, что в Scala невозможно написать какую-либо ковариантную изменяемую структуру, которая имеет ковариантные операции, поэтому вы будете ограничены несколькими операциями изменения, такими как swap, которые не принимают в качестве параметра A, но если вы попробуете реализовать set_2 не сможешь. - person Mikel San Vicente; 21.03.2018