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
The core problem here is that you want to create a heterogenous list, something like List[Int, String]
instead of List[Any]
. This means you need a different structure that would preserve Int
and String
types, but still would be "mappable" like List. The one structure in scala-library that can contain heterogenous types is Tuple:
val tuple = (1, "abc")
val result = List(implicitly[Show[Int]].show(tuple._1), implicitly[Show[Int]].show(tuple._2))
However, scala-library can't map
over tuples - you might want some syntax sugar for better readability.
So the obvious solution is HList from Shapeless: Int :: String :: HNil
(or you can use tuple ops and stay with (Int, String)
)
import shapeless._
import poly._
//show is a polymorphic function
//think of it as `T => String` or even `(Show[T], T) => String`
object show extends Poly1 {
implicit def atT[T: Show] = at[T](implicitly[Show[T]].show)
}
@ (1 :: "aaaa" :: HNil) map show
res8: String :: String :: HNil = "int 1" :: "aaaa" :: HNil
Or you could use at[Int]
/at[String]
instead of type-classes, like in @Steve Robinson's answer.
P.S. The lib could be found here. They also provide one-liner to get Ammonite REPL with shapeless integrated, so you could try my example out using:
curl -s https://raw.githubusercontent.com/milessabin/shapeless/master/scripts/try-shapeless.sh | bash
Notes:
Practically Shapeless solution requires as same amount of maintenance as Tuple-based one. This is because you have to keep track of your Int
and String
types anyways - you can never forget about those (unlike in homogenous List[T]
case). All Shapeless does for you is nicer syntax and sometimes better type inference.
If you go with tuples - you can improve readability by using implicit class
instead of Haskell-like style, or if you still want Haskell-like, there is a Simulacrum macro for better type-class syntax.
Given that other scala-library-only alternatives just capture type class instances inside some regular class, you could be better off with a regular OOP wrapper class:
trait Showable[T]{def value: T; def show: String}
class IntShow(val value: Int) extends Showable[Int]{..}
class StringShow(val value: String) extends Showable[String] {..}
val showables: List[Showable[_]] = List(new Showable(5), new Showable("aaa"))
showables.map(_.show)
Looks cleaner and more readable to me :)
If you like to rewrite dynamic dispatching in FP-style:
sealed trait Showable
final case class ShowableInt(i: Int) extends Showable
final case class ShowableString(s: String) extends Showable
implicit class ShowableDispatch(s: Showable){
def show = s match{ //use `-Xfatal-warnings` scalac option or http://www.wartremover.org/ to guarantee totality of this function
case ShowableInt(i) => ...
case ShowableString(s) => ...
}
}
List(ShowableInt(5), ShowableString("aaa")).map(_.show)
If you really want static dispatching (or ad-hoc polymorphism), given that other solutions introduce Showable[_]
which is practically Showable[Any]
:
case class Showable[T](v: T, show: String)
def showable(i: Int) = Showable(i, s"int $i")
def showable(s: String) = Showable(i, s)
List(showable(5), showable("aaa")).map(_.show)