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 вызывает оператор::.
Итак, мой вопрос в том, что является лучшим решением этой проблемы?
Мой предварительный вывод состоит в том, что неявное предположение должно быть отключено на сайте определения, где семантика в противном случае неверно набрана. Я дам ответ, который показывает, как отключить неявное подчинение на сайте определения метода. Существуют ли альтернативные решения?
Обратите внимание, что эта проблема является общей и не изолирована от списков.
UPDATE: отправил запрос на улучшение и начал scala обсуждение темы. Я также добавил комментарии в ответах Ким Стебеля и Питера Шмитца, в которых показано, что их ответы имеют ошибочную функциональность. Таким образом, решения нет. Кроме того, в вышеупомянутой теме обсуждения я объяснил, почему я считаю, что ответ сотовой связи неверен.