Type-aware generics in Scala

It is possible to create a generic class, that would have parts of it aware of the actual class of its type argument. Without reflection and linked in compile time. You may need to implement class-specific optimization. In this example we will be implementing a generic Garage, that would optimize some of its operations depending on the actual type of the cars parked in it.

Update: the proper name for this technique is typeclass pattern.

Let us start by defining some common functionality in the regular way we would do for a generic:

trait Car {}

case class Bus(val name: String) extends Car{}
case class FireEngine(val name: String) extends Car {}
case class Tractor(val name: String) extends Car {
  def isBroken = false
}

class Garage[T <: Car] {

  private[demo] var cars = List[T]()

  def park(car: T) {
    cars = car :: cars
  }

  def size = cars.size

  def takeOneOut: T = ???
}

So far nothing special. But let's say we want our "takeOneOut" method to be type-aware. Meaning that it would return different cars depending on the type of the cars parked in the garage. Let's say we want to take the first Bus, find a non-broken tractor and the least recently used fire engine. We can do it:

class Garage[T <: Car] {
  /* ... skipped ... */
  def takeOneOut(implicit helper: GarageHelper[T]): T =
    helper.takeOne(this)
}

trait GarageHelper[T <: Car] {
  def takeOne(garage: Garage[T]): T
}

object BusGarageHelper extends GarageHelper[Bus] {
  // Take the first bus
  def takeOne(garage: Garage[Bus]) = {
    val selected = garage.cars.head
    garage.cars = garage.cars.tail
    selected
  }
}

object FireEngineGarageHelper extends GarageHelper[FireEngine] {
  // Take the least recently used one
  def takeOne(garage: Garage[FireEngine]) = {
    val selected = garage.cars.last
    garage.cars = garage.cars.dropRight(1)
    selected
  }
}

object TractorGarageHelper extends GarageHelper[Tractor] {
  // Take first non broken tractor
  def takeOne(garage: Garage[Tractor]) = {
    val selected = garage.cars.find(!_.isBroken).get
    garage.cars = garage.cars.filter(_ == selected)
    selected
  }
}

We have defined a type specific helper for each of the known subclasses of Car and wired it in via implicit parameter. Well, actually not quite, we still need to add few more lines. This time to the package object:

package object demo {

  implicit val busHelper = BusGarageHelper
  implicit val fireEngineHelper = FireEngineGarageHelper
  implicit val tractorHelper = TractorGarageHelper

}

Let's make sure we can use it for real:

import demo._

val g = new Garage[Bus]
g.park(Bus("1"))
g.park(Bus("2"))
g.takeOneOut //returns Bus(2)

val g2 = new Garage[FireEngine]
g2.park(FireEngine("1001"))
g2.park(FireEngine("1002"))
g2.takeOneOut // returns FireEngine(1001)

See: the logic to park the car is the same for all kinds of cars. Garage is a generic in the regular sense. But the logic behind taking the cars out is specific for each class. The function works for the known subtypes of the trait Car, of course. We may want to seal that trait to protect the user. Or, if the user of our generic Garage would need to use some other subtype of Car, she would need to write an appropriate helper.

The best part is that the wiring is done at compile time, bearing next to zero performance overhead at the runtime. That method works well with Union Types.