Reputation: 1006
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 Helper
s:
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
?
Upvotes: 1
Views: 289
Reputation: 14224
In Scala context bound like class A[T: Typeclass]
is just syntactic sugar for class A[T](implicit ev: Typeclass[T])
. Unlike T <: Base
or T >: Super
, context bound is not really a part of a type signature, so you can't have a signature like val b: Box[T: Typeclass]
.
If you want to run typeclass operations on elements of some container, you'd have to pack relevant typeclass instances together with the values in the container.
A possible implementation of this may look as follows:
import language.higherKinds
import language.implicitConversions
// Class that packs values with typeclass instances
class WithTC[T, TC[_]](t: T)(implicit tc: TC[T]) {
// Some helper methods to simplify executing typeclass operations
// You may just make `t` and `tc` public, if you wish.
def apply[U](op: (TC[T], T) => U) = op(tc, t)
def apply[U](op: T => TC[T] => U) = op(t)(tc)
}
object WithTC {
// Implicit conversion to automatically wrap values into `WithTC`
implicit def apply[T, TC[_]](t: T)(implicit tc: TC[T]): WithTC[T, TC] =
new WithTC(t)(tc)
}
Then you can make a sequence with existentially typed elements:
import Helpers._
val data: Seq[(T WithTC Helper) forSome { type T }] = Seq(new X(), new Y())
And execute typeclass operations on the sequence elements:
// The following lines produce equivalent results
data.foreach(_(_ help _))
data.foreach(_(t => implicit tc => Helpers.help(t)))
data.foreach(_(t => Helpers.help(t)(_)))
Upvotes: 1
Reputation: 1239
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
Upvotes: 0