List("a").contains(5)
Поскольку Int
никогда не может содержаться в списке String
, этот должен генерировать ошибку во время компиляции, но этого не происходит.
Он расточительно и незаметно проверяет каждое String
, содержащееся в списке, на соответствие 5
, что никогда не может быть истинным ("5"
никогда не равно 5
в Scala).
Это было названо "проблема" содержит " а> ". И некоторые подразумевали, что если система типов не может правильно ввести такую семантику , тогда зачем прилагать дополнительные усилия для обеспечения типов. Поэтому я считаю, что это важная проблема, которую необходимо решить.
Параметризация типа B >: A
из List.contains
вводит любой тип, который является супертипом типа A
(тип элементов, содержащихся в списке).
trait List[+A] {
def contains[B >: A](x: B): Boolean
}
Параметризация этого типа необходима, поскольку +A
объявляет список ковариантным для типа A
, поэтому A
не может использоваться в контравариантной позиции, то есть как тип входного параметра. Ковариантные списки (которые должны быть неизменными) гораздо более эффективны для расширения, чем инвариантные списки ( который может быть изменяемым).
A
- это String
в проблемном примере выше, но Int
не является супертипом String
, так что же произошло? неявное подчинение в Scala решило, что Any
является взаимным супертипом и String
, и Int
.
Создатель Scala Мартин Одерски, предложил исправление чтобы ограничить тип ввода B
только теми типами, у которых есть метод equals, которого нет у Any
.
trait List[+A] {
def contains[B >: A : Eq](x: B): Boolean
}
Но это не решает проблему, потому что два типа (где тип ввода не является супертипом типа элементов списка) могут иметь общий супертип, который является подтипом Any
, то есть также подтипом 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 вызывает оператор ::.
Итак, мой вопрос: как лучше всего решить эту проблему?
Мое предварительное заключение состоит в том, что неявное подчинение должно быть отключено на сайте определения, где семантика в противном случае типизирована неправильно. Я дам ответ, который покажет, как отключить неявное подчинение на сайте определения метода. Есть ли альтернативные решения?
Обратите внимание, что эта проблема носит общий характер и не ограничивается списками.
ОБНОВЛЕНИЕ: я отправил запрос на улучшение и начал ветку обсуждения scala по этому поводу. Я также добавил комментарии под ответами Ким Стебель и Петера Шмитца, показывающие, что их ответы имеют ошибочную функциональность. Таким образом, решения нет. Также в вышеупомянутой ветке обсуждения я объяснил, почему я считаю, что ответ soc неправильный.