Search code examples
scalagenericswildcardimplicit

How to enforce a context bound on a wildcard in Scala?


I have an implicit helper set up like this:

trait Helper[T] {
  def help(entry: T): Unit
}

object Helpers {
  implicit object XHelper extends Helper[X] {
    override def help(entry: X): Unit = {println("x")}
  }
  implicit object YHelper extends Helper[Y] {
    override def help(entry: Y): Unit = {println("y")}
  }

  def help[T](entry: T)(implicit helper: Helper[T]): Unit = {
    helper.help(entry)
  }
}

I would like to set up a collection of elements and run help on each of them. However, the following gives a compiler error because we can't guarantee all elements have matching Helpers:

val data = Seq[_](new X(), new Y())
data.foreach(entry => Helpers.help(entry))

If we had a generic type T we could enforce the implicit constraint on it with [T: Helper], but that doesn't work on _. How can I enforce that each element of data has a matching Helper?


Solution

  • It's not possible with type like Seq since it is only parametrized for one element type that is common for all its elements.

    However, you can achieve this with Shapeless HLists and polymorphics functions (Poly):

    class X
    class Y
    
    trait Helper[T] {
      def help(entry: T): Unit
    }
    
    object Helpers {
      implicit object XHelper extends Helper[X] {
        override def help(entry: X): Unit = println("x")
      }
    
      implicit object YHelper extends Helper[Y] {
        override def help(entry: Y): Unit = println("y")
      }
    }
    
    import shapeless._
    
    object helper extends Poly1 {
      implicit def tCase[T: Helper]: Case.Aux[T, Unit] = 
        at(implicitly[Helper[T]].help(_))
    }
    
    val hlist = new X :: new Y :: HNil
    hlist.map(helper)
    
    // Output:
    x
    y