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