Выборочно отключить подчинение в Scala? (правильно введите List.contains)

List("a").contains(5)

Поскольку Int никогда не может содержаться в списке String , этот должен генерировать ошибку во время компиляции , но это не так.

Он расточительно и незаметно проверяет каждую строку , содержащуюся в списке, на равенство 5 , что никогда не может быть истинным ( «5» никогда не равно ] 5 в Scala).

Это было названо « проблема« содержит »». И некоторые подразумевали , что если система типов не может правильно ввести такую ​​семантику, тогда зачем прилагать дополнительные усилия для обеспечения типов. Поэтому я считаю, что это важная проблема, которую необходимо решить.

Параметризация типа B>: A из List.contains вводит любой тип, который является супертипом типа A (тип содержащихся элементов в списке).

trait List[+A] {
   def contains[B >: A](x: B): Boolean
}

Параметризация этого типа необходима, потому что + A объявляет, что список ковариантен для типа A , таким образом, A может не может использоваться в контравариантной позиции, т.е. как тип входного параметра. Ковариантные списки (которые должны быть неизменными) гораздо более эффективны для расширения , чем инвариантные списки (которые могут быть изменяемыми).

- это String в проблемном примере выше, но Int не является супертипом String , так что же произошло? Неявное подчинение в Scala решило, что Any является взаимным супертипом как String , так и Int .

Создатель Scala, Мартин Одерски, предложил , что исправление будет заключаться в ограничении типа ввода B только теми типами, у которых есть метод равенства, который Any не имеет.

trait List[+A] {
   def contains[B >: A : Eq](x: B): Boolean
}

Но это не решает проблему, потому что два типа (где тип ввода не является супертипом типа элементов списка) могут иметь общий супертип, который является подтипом Любой , то есть также подтип Eq . Таким образом, он будет компилироваться без ошибок, и неправильно типизированная семантика останется.

Отключение неявного подчинения везде, где не является идеальным решением , потому что неявное подчинение - вот почему следующий пример для подчинения Any работает. И мы не хотели бы, чтобы нас заставляли использовать приведение типов, когда принимающий сайт (например, передавая в качестве аргумента функции) имеет правильно типизированную семантику для общего супертипа (который может даже не быть Any ).

trait List[+A] {
   def ::[B >: A](x: B): List[B]
}

val x : List[Any] = List("a", 5) // see[1]

[1] List.apply вызывает :: operator .

Итак, мой вопрос: как лучше всего решить эту проблему?

Мое предварительное заключение состоит в том, что неявное подчинение должно быть отключено на сайте определения, где семантика в противном случае типизирована неправильно.Я дам ответ, который покажет, как отключить неявное подчинение на сайте определения метода. Есть ли альтернативные решения?

Обратите внимание, что это общая проблема, которая не ограничивается списками.

ОБНОВЛЕНИЕ : Я подал запрос на улучшение и начал обсуждение scala по этому . Я также добавил комментарии под ответами Ким Стебель и Петера Шмитца, показывающие, что их ответы имеют ошибочную функциональность. Таким образом, решения нет. Также в вышеупомянутой ветке обсуждения я объяснил, почему я считаю, что ответ soc неправильный.

22
задан Community 23 May 2017 в 12:19
поделиться