-
-
Save bvenners/8469906 to your computer and use it in GitHub Desktop.
scala> List(1, 2, 3) contains 1 | |
res5: Boolean = true | |
scala> List(1, 2, 3) contains "1" | |
res6: Boolean = false | |
scala> import scala.collection.GenSeq | |
import scala.collection.GenSeq | |
scala> implicit class Containz[A](xs: GenSeq[A]) { | |
| def containz[B <: A](ele: B): Boolean = xs.exists(_ == ele) | |
| } | |
defined class Containz | |
scala> List(1, 2, 3) containz 1 | |
res7: Boolean = true | |
scala> List(1, 2, 3) containz "1" | |
<console>:13: error: inferred type arguments [String] do not conform to method containz's type parameter bounds [B <: Int] | |
List(1, 2, 3) containz "1" | |
^ | |
<console>:13: error: type mismatch; | |
found : String("1") | |
required: B | |
List(1, 2, 3) containz "1" | |
^ |
Yeah, but you are cheating Paul. Anyway, mercy apart, your workaround doesn't break Set and Map (arguably the collections on which you want to call contains
), as they are invariant in the key:
implicit class SetIncludes[A](set: collection.Set[A]) {
def includes[B <: A](elem: B): Boolean = set.contains(elem)
}
Set(1, 2, 3) includes 1 // true
Set(1, 2, 3) includes "1" // rejected
SetIncludes[Any](Set(1, 2, 3): Set[Int]) // rejected
Yep, this approach with an implicit class is probably the most convenient way of casting off the variance that cripples type checking for contains.
Oops, deleted my comment by mistake while trying to get left square brackets to show up. Dang. It was something like:
I eventually realized that the reason my implicit conversion worked in my initial example is that I'd gotten rid of covariance. List[Int] is covariant, but Containz[Int] is invariant. When I tried the same B <: A trick on an actually covariant type, I hit the problem:
scala> // This does not compile
scala> class MySeq1[+A] {
| def contains[B <: A](ele: B): Boolean = true
| }
<console>:8: error: covariant type A occurs in contravariant position in type <: A of type B
def containz[B <: A](ele: B): Boolean = true
^
scala>
scala> // This compiles, but of course will slam to Any
scala> class MySeq2[+A] {
| def containz[B >: A](ele: B): Boolean = true
| }
defined class MySeq2
scala>
scala>
scala> class GoForIt\[A, B]
defined class GoForIt
scala>
scala> implicit def evidence[A, B](implicit ev: B <:< A): GoForIt[A, B] = new GoForIt[A, B]
evidence: [A, B](implicit ev: <:<[B,A])GoForIt [A,B]
scala>
scala> // This does not compile
scala> class MySeq3[+A] {
| def containz[B](ele: B)(implicit ev: GoForIt\A, B]): Boolean = true
| }
<console>:10: error: covariant type A occurs in invariant position in type GoForIt\[A,B] of value ev
def containz[B](ele: B)(implicit ev: GoForIt[A, B]): Boolean = true
^
So it seems the best way to have a type safe contains method on covariant Seq's in Scala is via an implicit conversion.
The problem is that you are at the mercy of type inference. With a little nudge, you're back where you started.