[ ].blue

Challenges with Implicits for Declarative Programming

August 1, 2014

I really want to like Scala implicits but I just keep bumping into limitations with them. The one which has become the most annoying is that implicits only look up what the current "view" of the type is, not the "most specific" of what the type is. Here's a quick example...

1
2
3
4
5
6
7
trait Person
class Manager extends Person
class Engineer extends Person

implicit class JsonOps[A](any: A) {
  def toJson(implicit convert: JsonConverter[A]) = convert(any)
}

What's happening here is we have a base type, Person, and two child types, Manager and Engineer. There is an implicit class that is providing a toJson method to everything where toJson will look up an implicit JsonConverter of type A for whatever type it's called on. This is a fairly straight forward way of saying, "everything should have a toJson method".

It works because the compiler will assert for us that there is always an implicit JsonConverter of whatever A we have in scope during compilation. For example, If I have val engy = new Engineer and call engy.toJson, then Scala will look for JsonConverter[Engineer] and all is good.

In theory this should allow us to write code declaratively, describing once how Engineer JSON is created and leaving it at that. However, what if I have a list of Person's?

1
2
val people = Seq(new Engineer, new Manager)
people.map(_.toJson)

What happens is that people actually is of type Seq[Person] (Seq is covariant). At that point the type system "sees" the new Engineer and new Manager as type Person not as types Engineer and Manager. That means that when toJson is called from within the map when it his the new Engineer, Scala will look for an implicit JsonConverter[Person] which is not what we want.

The only work around I've come up with for this is to use some kind of dynamic dispatch...

1
2
3
4
5
val people = Seq(new Engineer, new Manager)
people.map {
  case e: Engineer => e.toJson
  case m: Manager  => m.toJson
}

This ultimately defeats the entire goal of declarative programming because now I'm back to describing flow.