How to define a scala method with type param that cannot be Any
Asked Answered
G

1

9

In below example, I want to define a contains method that doesn't compile if a and b are not of the same base type.

  • In contains1 impl, if a is Seq[Int] and b is String, T is derived to be Any, and it compiles. This is not I want.
  • In contains2 impl, if a is Seq[Int] and b is String, then it doesn't compile. The behavior is what I want.
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

However, is there a simpler way to achieve the same behaviour as in contains2? Ordering context bound confuses me as the method has nothing to do with sorting/ordering at all.

Grape answered 17/6, 2019 at 18:32 Comment(2)
Type inference works using the unification algorithm, and it work really hard to ensure things compile... sometimes, harder than one would want, like this case. One workaround is to define it like this: def contains[T](a: Seq[T])(b: T): Boolean = a.contains(b). In this case, since b is in a separate argument list, it is not take into account when inferring the type parameter T, in really the already inferred type T is used, as such, if b is not of the same type as the elements in the collection, it will not compile. - Additionally, one advice, add the -Xlint:infer-any flag.Weixel
As @LuisMiguelMejíaSuárez says, splitting up the arguments is probably the simplest fix. As an alternative, though, you could do something like def contains2[T: Eq](a: Seq[T], b: T): Boolean = a.exists(Eq[T].eqv(b, _)) with Cats's Eq, where the constraint is both meaningful and actually used in the operation. (Unfortunately Any has a scala.math.Equiv instance, so that's not really useful here.)Gaylord
B
9

You could use generalized type constraints operator =:=.

For example:

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

and then:

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

More on generalized type constraints here and here.

As LuisMiguelMejíaSuárez noticed, you could also consider using B <:< A instead of A =:= B. I won't elaborate on differences between these two because it's described in linked answer and article, but in brief, <:< would also allow all B that are a subtype of A, while =:= needs types to match exactly.

Becki answered 17/6, 2019 at 19:10 Comment(5)
If going the Generalized Type Constraints way, I would use implicit ev: B <:< A which would give more flexibility, while maintaining type safety.Weixel
@LuisMiguelMejíaSuárez Sure. I edited my answer. Thanks for the remark.Avertin
"all A that are a subtype of B", in this case that would not make much sense, since A is the type parameter of the collection. It would make more sense as "all B that are a subtype of A", but then you would need to flip the arguments, as I did in my comment ;)Weixel
I guess it would make the most sense if you could do something like A <:< B || B <:< A, because then you could do contains(Seq(Seq("one")), List("four")) and contains(Seq(List("one")), Seq("four")), but I think I can't be done :)Avertin
I do not know if it can be done, but I do not think it would make more sense. A <:< B means A could be a subtype of B, in this case that means that a collection of a "inferior" type (in the sense of a scale of concreteness) could contain any "superior" value... that could lead to a: Collection of Ints could contain a String, since B could be inferred as Any... which is the original problem. On the other hand, B <:< A would only mean that a collection of Pets can contain a Dog (which by the definition of the Liskov principle, must be true).Weixel

© 2022 - 2024 — McMap. All rights reserved.