Polymorphism with Scala type classes
functional-programming, oop, polymorphism, scala, typeclass
Solution
I think type classes are not compatible with your scenario. They are useful when the types are disjoint, but you actually require that the instances are reflecting a super-type/sub-type hierarchy and are not independent.
With this refactoring, you are just creating the danger of the wrong instance being picked:
trait Foo
case class Bar() extends Foo
trait HasBaz[A] { def baz: Set[Any] }
implicit object FooHasBaz extends HasBaz[Foo] { def baz = Set("foo") }
implicit object BarHasBaz extends HasBaz[Bar] { def baz = FooHasBaz.baz + "bar" }
def test[A <: Foo](x: A)(implicit hb: HasBaz[A]): Set[Any] = hb.baz
val bar: Foo = Bar()
test(bar) // boom!
So you ended up re-writing the polymorphic dispatch with your pattern matcher in `SuperClassMethod`. You basically go OO -> FP -> OO, while rendering the idea of type classes unusable (to be open), ending up rather in a sum type (all sub types known).
Problem
We are refactoring an inherited `method` to use a type class instead - we would like to concentrate all of the `method` implementations in one place, because having them scattered among the implementing classes is making maintenance difficult. However, we're running into some trouble as we're fairly new to type classes. At present `method` is defined as ``` trait MethodTrait { def method: Map[String, Any] = // default implementation } abstract class SuperClass extends MethodTrait { override def method = super.method ++ // SuperClass implementation } class Clazz extends SuperClass { override def method = super.method ++ // Clazz implementation } ``` and so on, where there are a total of 50+ concrete classes, the hierarchy is fairly shallow (`abstract class SuperClass` -> `abstract class SubSuperClass` -> `abstract class SubSubSuperClass` -> `class ConcreteClass` is as deep as it goes), and a concrete class never extends another concrete class. (In the actual implementation, `method` returns a Play Framework `JsObject` instead of a `Map[String, Any]`.) We're trying to replace this with a type class: ``` trait MethodTrait[T] { def method(target: T): Map[String, Any] } class MethodType { type M[T] = MethodTrait[T] } implicit object Clazz1Method extends MethodTrait[Clazz1] { def method(target: Clazz1): Map[String, Any] { ... } } implicit object Clazz2Method extends MethodTrait[Clazz2] { def method(target: Clazz2): Map[String, Any] { ... } } // and so on ``` I'm running into two problems: A. Mimicking the `super.method ++` functionality from the previous implementation. At present I'm using ``` class Clazz1 extends SuperClass class Clazz2 extends SubSuperClass private def superClassMethod(s: SuperClass): Map[String, Any] = { ... } private def subSuperClassMethod(s: SubSuperClass): Map[String, Any] = { superClassMethod(s) ++ ... } implicit object Clazz1Method extends MethodTrait[Clazz1] { def method(target: Clazz1): Map[String, Any] = { superClassMethod(target) ++ ... } } implicit object Clazz2Method extends MethodTrait[Clazz2] { def method(target: Clazz2): Map[String, Any] = { subSuperClassMethod(target) ++ ... } } ``` but this is ugly, and I won't get a warning or error if I accidentally call a method too far up the hierarchy e.g. if `Clazz2` calls `superClassMethod` instead of `subSuperClassMethod`. B. Calling `method` on a superclass, e.g. ``` val s: SuperClass = new Clazz1() s.method ``` Ideally I'd like to be able to tell the compiler that every subclass of `SuperClass` has a corresponding implicit object for `method` in the type class and so `s.method` is type-safe (or I'll get a compile time error if I've neglected to implement a corresponding implicit object for a subclass of `SuperClass`), but instead all I've been able to come up with is ``` implicit object SuperClassMethod extends MethodTrait[SuperClass] { def method(target: SuperClass): Map[String, Any] = { target match { case c: Clazz1 => c.method case c: Clazz2 => c.method ... } } } ``` which is ugly and won't give me a compile-time warning or error if I've omitted a class since I can't define `SuperClass` as a sealed trait. We'd be open to alternatives to type classes that would allow us to concentrate the `method` code in one place. `method` is only being called from two places: A. Other `method` implementations, for example `Clazz1` has a `val clazz2: Option[Clazz2]`, in which case the `method` implementation in `Clazz1` would be something like ``` def method = super.method ++ /* Clazz1 method implementation */ ++ clazz2.map(_.method).getOrElse(Map()) ``` B. The top level Play Framework controller (i.e. the abstract class from which all of the controllers inherit), where we've defined a three `ActionBuilders` that call `method`, e.g. ``` def MethodAction[T <: MethodTrait](block: Request[AnyContent] => T) = { val f: Request[AnyContent] => SimpleResult = (req: Request[AnyContent]) => Ok(block(req).method) MethodActionBuilder.apply(f) } ```