We will refactorize the inherited method to use the type class instead - we would like to concentrate all method implementations in one place, since their scatter across implementation classes makes maintenance difficult. However, we encounter some problems, as we are fairly new to introduce classes. method is currently 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 }
etc., where there are only 50+ specific classes, the hierarchy is pretty shallow ( abstract class SuperClass β abstract class SubSuperClass β abstract class SubSubSuperClass β class ConcreteClass is as deep as it is), and a particular class never extends another specific class . (In the actual implementation, method returns a JsObject playback JsObject instead of Map[String, Any] .) We are trying to replace this with a class of type:
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] { ... } }
I have two problems:
A. super.method ++ functionality of super.method ++ from a previous implementation. I am currently 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 in the hierarchy, for example. if Clazz2 calls superClassMethod instead of subSuperClassMethod .
B. Calling method on a superclass, for example
val s: SuperClass = new Clazz1() s.method
Ideally, I would like to tell the compiler that each subclass of SuperClass has a corresponding implicit object for method in the type class, so s.method is type safe (or I will get a compile-time error if I neglected to inject the corresponding implicit object for the SuperClass subclass), but instead, all I could find was
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 will not give me warnings or compile-time errors if I omit the class, as I cannot define SuperClass as a sealed trait.
We will be open to alternatives to class types that allow us to concentrate method code in one place. method is called from only two places:
a. Other method implementations, such as Clazz1 have val clazz2: Option[Clazz2] , in which case the implementation of method in Clazz1 will be something like
def method = super.method ++ ++ clazz2.map(_.method).getOrElse(Map())
B. The top-level Play Framework controller (i.e., the Abstract class from which all controllers are inherited), where we defined three ActionBuilders that call method , for example
def MethodAction[T <: MethodTrait](block: Request[AnyContent] => T) = { val f: Request[AnyContent] => SimpleResult = (req: Request[AnyContent]) => Ok(block(req).method) MethodActionBuilder.apply(f) }