Как определить метод scala с параметром типа, который не может быть Any

В приведенном ниже примере я хочу определить метод contains, который не компилируется, если a и b не относятся к одному и тому же базовому типу.

  • В contains1 impl, если a равно Seq[Int], а b равно String, T выводится как Any и компилируется. Это не я хочу.
  • В contains2 impl, если a равно Seq[Int], а b равно String, то он не компилируется. Поведение такое, какое я хочу.
def contains1[T](a: Seq[T], b: T): Boolean = a.contains(b)

println(contains1(Seq(1,2,3), "four")) // false

def contains2[T: Ordering](a: Seq[T], b: T): Boolean = a.contains(b)

println(contains2(Seq(1,2,3), "four")) // compilation error
// cmd7.sc:1: No implicit Ordering defined for Any.
// val res7 = isMatched(Seq(1,2,3), "s")
                    ^
// Compilation Failed

Однако есть ли более простой способ добиться того же поведения, что и в contains2? Ordering контекстная привязка меня смущает, поскольку этот метод вообще не имеет ничего общего с сортировкой/упорядочением.


person Yik San Chan    schedule 17.06.2019    source источник
comment
Вывод типов работает с использованием алгоритма унификации, и он очень усердно работает, чтобы обеспечить компиляцию... иногда сложнее, чем хотелось бы, как в этом случае. Один из обходных путей — определить его следующим образом: def contains[T](a: Seq[T])(b: T): Boolean = a.contains(b). В этом случае, поскольку b находится в отдельном списке аргументов, он не учитывается при выводе параметра типа T, в действительности уже выведенный тип T используется как таковой, если b не того же типа, что и элементы в коллекции, он не будет компилироваться. - Дополнительно один совет, добавьте флаг -Xlint:infer-any.   -  person Luis Miguel Mejía Suárez    schedule 17.06.2019
comment
Как говорит @LuisMiguelMejíaSuárez, разделение аргументов, вероятно, является самым простым решением. Однако в качестве альтернативы вы можете сделать что-то вроде def contains2[T: Eq](a: Seq[T], b: T): Boolean = a.exists(Eq[T].eqv(b, _)) с Cats Eq, где ограничение имеет смысл и фактически используется в операции. (К сожалению, у Any есть экземпляр scala.math.Equiv, так что здесь это бесполезно.)   -  person Travis Brown    schedule 17.06.2019


Ответы (1)


Вы можете использовать оператор ограничения обобщенного типа =:=.

Например:

def contains[A,B](a: Seq[A], b: B)(implicit evidence: A =:= B): Boolean = a.contains(b)

а потом:

println(contains1(Seq(1,2,3), "four")) //fails with Cannot prove that Int =:= String.
println(contains1(Seq("one"), "four")) //returns false
println(contains1(Seq("one", "four"), "four")) //true

Подробнее об ограничениях обобщенного типа здесь и здесь.

Как заметил LuisMiguelMejíaSuárez, вы также можете использовать B <:< A вместо A =:= B. Я не буду подробно рассказывать о различиях между этими двумя, потому что это описано в связанном ответе и статье, но, вкратце, <:< также разрешит все B, которые являются подтипом A, в то время как =:= требует, чтобы типы точно совпадали.

person Krzysztof Atłasik    schedule 17.06.2019
comment
Если пойти по пути Обобщенные ограничения типов, я бы использовал implicit ev: B <:< A, что дало бы больше гибкости, сохраняя при этом безопасность типов. - person Luis Miguel Mejía Suárez; 17.06.2019
comment
@LuisMiguelMejíaSuárez Конечно. Я отредактировал свой ответ. Спасибо за замечание. - person Krzysztof Atłasik; 17.06.2019
comment
все A, которые являются подтипом B, в данном случае это не имеет особого смысла, поскольку A является параметром типа коллекции. Это имело бы больше смысла, поскольку все B являются подтипом A, но тогда вам нужно было бы перевернуть аргументы, как я сделал в своем комментарии;) - person Luis Miguel Mejía Suárez; 17.06.2019
comment
Я думаю, было бы разумнее, если бы вы могли сделать что-то вроде A <:< B || B <:< A, потому что тогда вы могли бы сделать contains(Seq(Seq("one")), List("four")) и contains(Seq(List("one")), Seq("four")), но я думаю, что я не могу этого сделать :) - person Krzysztof Atłasik; 17.06.2019
comment
Я не знаю, можно ли это сделать, но я не думаю, что это будет иметь больше смысла. A <:< B означает, что A может быть подтипом B, в данном случае это означает, что коллекция низшего типа (в смысле шкалы конкретности) может содержать любое превосходящее значение... что может привести к a: Коллекция Ints может содержать String, поскольку B можно вывести как Any... что является исходной проблемой. С другой стороны, B <:< A будет означать только то, что коллекция Pets может содержать Собаку (что по определению принципа Лискова должно быть истинным)< /я>. - person Luis Miguel Mejía Suárez; 17.06.2019