Search code examples
scalashapeless

Scala : Iterate over type class objects


I am using several outside data types in a Scala program. My goal is to define polymorphic functions on these datatypes using type classes. The problem is that in the Scala program itself, these datatypes must be iterated over in sequence at certain points, which are the same points where I need to use the polymorphic functions from the type class. So for instance if we had

trait Show[A] {
  def show(a: A, b : String): String
}

object Show {

  def apply[A](implicit sh: Show[A]): Show[A] = sh

  def show[A: Show](a: A, b : String) = Show[A].show(a, b)

  implicit class ShowOps[A: Show](a: A) {
    def show(b : String) = Show[A].show(a, b)}


  implicit val blogCanShow: Show[Int] = new Show[Blog] {
    def show(blog: Blog, b : String): String = s"blog $blog" + b
}
  implicit val twitterCanShow: Show[String] = new Show[Twitter] {
    def show(twitter: Twitter, b : String): String = s"twitter $twitter" + b    }
}

Then I would need to use the datatypes like so:

for each data type:
  call show()
  business logic
  call another polymorphic function like show()
  more business logic
  etc... 

I tried using HLists from Shapeless, but couldn't quite figure out how to get a repeatable block of code to work with it. I think I need to use some sort of reflection here, but don't know precisely where to start. Any suggestions or help would be very much appreciated.


Solution

  • See http://tpolecat.github.io/2015/04/29/f-bounds.html for a famous discussion of this problem.

    The bottom line (towards the bottom of the post) is that you want something analogous to Seq[(A,Show[A]) forSome {type A}], so you have access to both the A and its Show. There's no getting around the need to store the Show[A]s without runtime reflection, but Rob shows a more graceful trick to contain the pair:

    trait ∃[F[_]] {
      type A
      val a: A
      val fa: F[A]
    }
    object ∃ {
      def apply[F[_], A0](a0: A0)(implicit ev: F[A0]): ∃[F] =
        new ∃[F] {
          type A = A0
          val a = a0
          val fa = ev
        }
    }
    

    so you can declare

    val shows: List[∃[Show]] = ∃(myBlog) :: ∃(myTweet) :: Nil
    

    and iterate over it, referring to the a and fa as needed.

    To me, this is preferable to the HList solution, since, while the code will initially look a bit opaque, any future reader's confusion can be quickly cleared up by Ctrl-right-clicking in an IDE.