Polymorphism with Scala Class Classes

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] { ... } } // and so on 

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 ++ /* Clazz1 method implementation */ ++ 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) } 
+6
source share
3 answers

I think type classes are incompatible with your script. They are useful when types do not intersect, but it is actually required that instances display a supertype / subtype hierarchy and not be independent.

With this refactoring, you simply create the danger of choosing the wrong instance:

 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 have finished rewriting the polymorphic dispatch with your template in SuperClassMethod . You mainly use OO β†’ FP β†’ OO, while concluding that type classes are unsuitable (to be open), ending more as a sum (all known subtypes).

+6
source

@ 0__ is on something - implicit permission occurs during compilation, so the instance of the type class that is used for this input will not depend on the type of execution time of this input.

To get the behavior you need, you need to write some implicit definition that will reflect the actual type of the object you want to call method on to select the correct typeclass instance.

I think this is more of a maintenance problem than what you have right now.

+2
source

Simply put: if you want to have your implementation in one place, you should use case classes for your hierarchy:

 abstract class SuperClass; case class Clazz(...) extends SuperClass; def method(o : SuperClass) : Map[String, Any] = o match { case Clazz( ... ) => defaultMethod ++ ... case ... } 

(Note that the method, of course, can be recursive) Since you may have an open sum type in scala (the compiler will not warn about missing templates), this should solve your problem without having to overuse the cool types.

+2
source

Source: https://habr.com/ru/post/959431/


All Articles