I would like to define a List
of elements implementing a common type class. E.g.
trait Show[A] {
def show(a: A): String
}
implicit val int
I will first sketch a solution, and then explain why the naive approach with List[Any](1, "abc")
cannot work.
What you can do
Define a wrapper class that can hold instances of type A
together with instances of Show[A]
:
case class Showable[A](a: A, showInst: Show[A]) {
def show: String = showInst.show(a)
}
Define your list as List[Showable[_]]
:
var showableList: List[Showable[_]] = Nil
Maybe define a separate method to fill this list (consider packing the list itself and the builder-method in a class):
def addShowable[A: Show](a: A): Unit = {
showableList ::= Showable[A](a, implicitly[Show[A]])
}
Alternatively, you can carefully add a (very tightly scoped) implicit conversion:
implicit def asShowable[A](a: A)(implicit s: Show[A]): Showable[A] =
Showable(a, s)
and then costruct your list as follows (note the explicit type ascription):
val showableList = List[Showable[_]](1, "abc")
Now you can go through the list and call show
:
showableList.map(_.show)
to obtain a list of String
.
What you cannot do
You cannot simply define
val list: List[Any] = List(1, "abc", , ..., )
and then expect to be able to call show
, because in order to call Show.show
, you need actual Show
instances. These things are not some type-hints that can be erased at runtime, they are actual objects, and they must be supplied by the compiler. Once you have created a List[Any]
, all is lost, because all the types are merged into an unexpressive upper bound Any
, and the compiler has no way to inject all the necessary implicits Show[T_1]
,..., Show[T_N]
. The argument is very similar to the third section "Dealing with implicits when defining interpreter for the Free monad" of this lengthy answer of mine.