Невозможно добавить элемент на карту, используя динамический тип mixin для ключа - PullRequest
6 голосов
/ 06 января 2012

Следующая инструкция компилируется нормально и работает как положено:

val map : Map[_ >: Int with String, Int] = Map(1 -> 2, "Hello" -> 3)

Однако, если я пытаюсь добавить на карту:

map + ((3,4))

или

map + (("Bye", 4))

тогда я получаю несоответствие типов:

найдено: java.lang.String ("Bye")

обязательно: _ $ 1, где тип _ $ 1>: Int с String

Если я ослаблю сигнатуру типа, чтобы разрешить Any в качестве типа Ключа, то все это будет работать, как и ожидалось.

Моя интуиция говорит, что это связано с невариантностьюключевого типа Map, и то, что _ $ 1 как-то исправляется как особый супертип Int with String, но я не особенно доволен этим.Кто-нибудь может объяснить, что происходит?

Отредактировано, чтобы добавить:

Если вам интересно, где это происходит, вы получите подпись, если сделаете что-то вроде:

val map = if (true) Map(1 -> 2) else Map("1" -> 2)

1 Ответ

9 голосов
/ 06 января 2012

Вы неправильно поняли Int with String.Это не объединение Int и String, это пересечение, а для Int и String оно пустое.Не наборы значений, которые являются Int с набором значений, которые являются строками, но набор значений, которые имеют характеристики Int с характеристиками String тоже.Там нет таких значений.

Вы можете использовать Either[Int, String] и иметь Map[Left(1) -> 2, Right("Hello") -> 3).Либо это не совсем Союз, это различаемый союз, A + B, а не AU B. Вы можете понять разницу в том, что либо [Int, Int] не то же самое, что Int.Это на самом деле изоморфно (Int, Boolean): у вас есть Int, и вы знаете, с какой стороны это тоже.Когда A и B являются дизъюнктами (как Int и String), A + B и AUB изоморфны.

Или (не для слабонервных) вы можете взглянуть на возможную кодировку объединениятипы Майлза Сабина.(Я не уверен, что вы могли бы действительно использовать это с существующим классом Map, и даже менее уверенно, чем вы должны даже пытаться, но тем не менее это делает чтение наиболее интересным).


Edit : прочитайте ваш вопрос и код слишком быстро, извините

Ваша нижняя граница Int with String такая же, как Nothing, поэтому Map[_ >: Int with String, Int], такая же, как Map[_ >: Nothing, Int] и Nothing подразумевается нижняя граница, это Map[_, Int].Ваш фактический Map является Map[Any, Int].Вы могли бы добавить логический ключ, он тоже работает, несмотря на Int с String.Map[Any, Int] может быть напечатано как Map[_, Int], так что ваша декларация val работает.Но ваш набор текста теряет всю информацию о типе ключа.Не зная, какой тип ключа, вы не можете ничего добавить (или извлечь) из таблицы.

UpperBound был бы не лучше, так как тогда не было бы возможных ключей.Даже первоначальное объявление val терпит неудачу.


Edit 2 : относительно if (true) Map(1 -> 2) else Map("1" -> 2)

Это не то же самое, что Map(1 -> 2, "1" -> 2).Это было проще, просто Map[Any, Int], так как Any является более распространенным супертипом Int и String.

С другой стороны, Map(1 -> 2) - это Map[Int, Int], а Map["1", 2] a Map[String, Int].Существует проблема поиска общего супертипа для Map[Int, Int] и Map[String, Int], который не находит общего супертипа для Int и String.

Давайте поэкспериментируем.Map является ковариантным по второму параметру.Если вы используете Int и String в качестве значений, а не ключей:

if (true) Map(1 -> 2) else Map(1 -> "2")
res1: scala.collection.immutable.Map[Int, Any]

С ковариацией просто берется общий супертип всех параметров типа.

С контравариантным типом

class L[-T]
object L{def apply[T](t: T) = new L[T])
class A
class B extends A
class C
if (true) L(new A) else L(new C)
res2: L[A with C]
if (true) L(new A) else L(new B)
res3: L[B]

принимает пересечение A with C. Когда B является подтипом A, A с B равно B.

Теперь с неизменяемым параметром Map, когда два типа связаны

if (true) Map(new A -> 1) else Map(new B -> 1)
res4: scala.collection.immutable.Map[_ >: B <: A, Int]

Такой тип не бесполезен.Вы можете получить доступ или добавить значения с помощью клавиш типа B.Но вы не можете получить доступ к значениям ключей A.Так как это то, что у вас есть на реальной карте (из-за true), неудачаЕсли вы получите доступ к keySet, он будет набран Set[A].У вас есть неполная информация о типе ключей, и то, что вы можете сделать, ограничено, но это необходимое ограничение, учитывая ограниченные знания о типе карты.С Int and String у вас есть минимальная информация, с нижней границей Any и верхней границей, эквивалентной Nothing.Верхняя граница Nothing делает невозможным вызов подпрограммы, которая принимает ключ в качестве параметра.Вы все еще можете получить keySet, с типом Set[Any], Any является нижней границей.

...