Skip to content

Instantly share code, notes, and snippets.

@bvenners
Created January 17, 2014 08:03
Show Gist options
  • Save bvenners/8469906 to your computer and use it in GitHub Desktop.
Save bvenners/8469906 to your computer and use it in GitHub Desktop.
Type checked contains method for GenSeq
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"
^
@paulp
Copy link

paulp commented Jan 17, 2014

The problem is that you are at the mercy of type inference. With a little nudge, you're back where you started.

scala> Containz[Any](List(1, 2, 3)) containz "1"
res6: Boolean = false

@Sciss
Copy link

Sciss commented Jan 17, 2014

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

@adriaanm
Copy link

Yep, this approach with an implicit class is probably the most convenient way of casting off the variance that cripples type checking for contains.

@bvenners
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment