scalatypeclassimplicits

Runtime cost of implicit definitions in Scala's typeclass pattern


I am looking at this article, on how Typeclasses are simulated in Scala using implicits.

If I am not wrong, a new instance is constructed on runtime every time a typeclass method for a recursive instance declaration is used (Printer a => Printer (Option a)). Here is the code, every time print is used a new Printer instance is created on runtime. Is there anyway to reuse the instance once created for a specific type (Option[Int] in this case)?

trait Printer[T] {
  def print(t: T): String
}

object Printer {
  implicit val intPrinter: Printer[Int] = new Printer[Int] {
    def print(i: Int) = s"$i: Int"
  }

  implicit def optionPrinter[V](implicit pv: Printer[V]): Printer[Option[V]] =
    new Printer[Option[V]] {
      println("New Option Printer")
      def print(ov: Option[V]) = ov match {
        case None => "None"
        case Some(v) => s"Option[${pv.print(v)}]"
      }
    }

}

object Main {
  def print[T](t: T)(implicit p: Printer[T]) = p.print(t)

  def main(args: Array[String]): Unit = {
    val res3 = print(Option(1))
    val res4 = print(Option(2))
    println(s"res3: ${res3}")
    println(s"res4: ${res4}")
  }
}

// New Option Printer
// New Option Printer
// res3: Option[1: Int]
// res4: Option[2: Int]

Solution

  • You are right. In your example an new instance is created with every call. However, there are several ways to deal with it. In my experience you end up with the following rule of thumb:

    First of all, do not optimize prematurely, make sure the additional instance are actually a problem. If you are sure it is performance relevant, you just cope by coding a lot of vals.

    object Printer {
      implicit val intPrinter: Printer[Int] = new Printer[Int] {
        def print(i: Int) = s"$i: Int"
      }
    
      // make sure this is not visible, as you do not want to have productive code create new instances on demand 
      private[this] def optionPrinter[V](implicit pv: Printer[V]): Printer[Option[V]] =
        new Printer[Option[V]] {
          println("New Option Printer")
          def print(ov: Option[V]) = ov match {
            case None => "None"
            case Some(v) => s"Option[${pv.print(v)}]"
          }
        }
    
      implicit val intOptPrinter: Printer[Option[Int]] = optionPrinter[Int]
    }
    

    I think, a more advanced solution is possible using shapeless. However, imho, for that a deep understanding of type classes and type level programming is required.