В примерах используется L
вместо List
или SeqLike
, поскольку для применения этого решения к ранее существовавшему методу contains
этих коллекций потребуется изменение ранее существовавшего кода библиотеки. Одна из целей - лучший способ достижения равенства, а не лучший компромисс для взаимодействия с текущими библиотеками (хотя необходимо учитывать обратную совместимость). Кроме того, моя другая цель состоит в том, чтобы этот ответ в целом применим для любой функции метода, которая хочет выборочно отключить функцию неявного подчинения компилятора Scala по любой причине, необязательно связанной с семантикой равенства.
case class L[+A]( elem: A )
{
def contains[B](x: B)(implicit ev: A <:< B) = elem == x
}
Приведенное выше генерирует ошибку по желанию, предполагая, что требуемая семантика для List.contains
означает, что ввод должен быть равен , а супертип содержится в элементе.
L("a").contains(5)
error: could not find implicit value for parameter ev: <:<[java.lang.String,Int]
L("a").contains(5)
^
Ошибка не генерируется, когда не требуется неявное включение.
scala> L("a").contains(5 : Any)
defined class L
scala> L("a").contains("")
defined class L
Это отключает неявное подчинение (выборочно на сайте определения метода), требуя, чтобы тип входного параметра B
совпадал с типом аргумента, передаваемого как вход (т. Е. Неявно подразделяется с A
), а затем отдельно требуется неявное доказательство того, что B
является или имеет неявно подразделяемый супертип A
.]
ОБНОВЛЕНИЕ 03 мая 2012 г. : Приведенный выше код неполон, как показано ниже, так как отключение всех подрасчетов на сайте определения метода не дает желаемого результата.
class Super
defined class Super
class Sub extends Super
defined class Sub
L(new Sub).contains(new Super)
defined class L
L(new Super).contains(new Sub)
error: could not find implicit value for parameter ev: <:<[Super,Sub]
L(new Super).contains(new Sub)
^
Единственный способ получить желаемую форму подчинения, это также приведение к методу (вызову) use-site.
L(new Sub).contains(new Super : Sub)
error: type mismatch;
found : Super
required: Sub
L(new Sub).contains(new Super : Sub)
^
L(new Super).contains(new Sub : Super)
defined class L
В ответ soc , текущая семантика для List.contains
состоит в том, что вход должен быть равен, но не обязательно, супертипу содержащегося элемента. Предполагается, что List.contains
обещает, что любой совпадающий элемент равен только, и не обязательно является копией (подтипа или) экземпляра входных данных. Текущий универсальный интерфейс равенства Any.equals : Any => Boolean
является однопроходным, поэтому равенство не обеспечивает отношения подтипов. Если это желательная семантика для List.contains
, отношения подтипов не могут быть использованы для оптимизации семантики во время компиляции, например отключение неявного включения, и мы застряли с потенциальной семантической неэффективностью, которая ухудшает производительность во время выполнения для List.contains
.
Хотя я буду больше изучать и размышлять о равенстве и содержать его, на самом деле мой ответ остается действительным для общей цели выборочного отключения неявного подчинения на сайте определения метода.
Мой мыслительный процесс также продолжается целостным образом. лучшая модель равенства.
Обновление : я добавил комментарий ниже ответ сока , так что теперь я думаю, что его точка зрения не имеет значения. Равенство всегда должно основываться на подтипных отношениях, которые, на самом деле, и есть то, что Мартин Одерский предлагает для нового пересмотра равенства (см. Также его версию из contains
). Любая специальная полиморфная эквивалентность (например, BitInt(1) == 1
) может обрабатываться с помощью неявных преобразований. Я объяснил в своем комментарии ниже ответ Didierd , что без моего улучшения ниже, предложенный Мартином contains
будет иметь семантическую ошибку, в результате чего взаимно неявно включенный в категорию супертип (отличный от Any
) выберет неверный неявный экземпляр Eq
(если таковой существует, иначе ненужная ошибка компилятора). Мое решение отключает неявное подчинение для этого метода, который является правильной семантикой для аргумента подтипа Eq.eq
.
trait Eq[A]
{
def eq(x: A, y: A) = x == y
}
implicit object EqInt extends Eq[Int]
implicit object EqString extends Eq[String]
case class L[+A]( elem: A )
{
def contains[B](x: B)(implicit ev: A <:< B, eq: Eq[B]) = eq.eq(x, elem)
}
L("a").contains("")
Примечание Eq.eq
может быть необязательно заменено на implicit object
(не переопределяется, поскольку нет виртуального наследования, см. Ниже).
Обратите внимание, что при желании L("a").contains(5 : Any)
больше не компилируется, поскольку Any.equals
больше не используется.
Мы можем сократить.
case class L[+A]( elem: A )
{
def contains[B : Eq](x: B)(implicit ev: A <:< B) = eq.eq(x, elem)
}
Add : x == y
должен быть вызовом виртуального наследования, т. Е. x.==
должен быть объявлен override
, поскольку в Eq
нет виртуального наследования класс типов. Параметр типа A
является инвариантным (поскольку A
используется в противоположной позиции в качестве входного параметра Eq.eg
). Тогда мы можем определить implicit object
на интерфейсе (a.k.a. trait
).
Таким образом, переопределение Any.equals
должно по-прежнему проверять, соответствует ли конкретный тип ввода. Эти накладные расходы не могут быть удалены компилятором.