Using implicits to inject a method at multiple levels of a trait hierarchy

230 Views Asked by At

I'm trying to take an existing hierarchy of traits (which I cannot control) and inject a method at the top level (and then override that method at each point in the hierarchy that needs custom treatment). These traits are used implicitly. Below is a trimmed down example that shows the trait hierarchy setup and implicit usage; and then my (failed) attempt to inject a method.


Define a Parent Trait with an unimplemented method, a Child Trait with a specified type and an implemented form of the method. Create an implicit of the Child Trait.

trait ParentTrait[T] {
  def doSomething1(x: T): T
}

trait ChildTrait extends ParentTrait[Double] {
  override def doSomething1(x: Double): Double = {
    return x + 1.0
  }
}

implicit object ChildTrait extends ChildTrait

Define a class and function to implicitly use Parent Trait and to call the method.

class Utilizer1() {
  def utilize[T](x: T)(implicit trt: ParentTrait[T]): Unit = {
    println(trt.doSomething1(x))
  }
}

Call the method, specifying the type (Double) which will pick up Child Trait implicitly. (This works fine.)

new Utilizer1().utilize[Double](1.0)

Here's where things don't work: "inject" a new method onto the Parent Trait, and override it in the Child Trait. (Clearly this is not the correct way to do this - how to accomplish this part?)

implicit class BetterParentTrait[T](trt: ParentTrait[T]) {
  def doSomething2(x: T): T = ???
}
implicit class BetterChildTrait(trt: ParentTrait[Double]) extends BetterParentTrait[Double](trt) {
  override def doSomething2(x: Double): Double = {
    return x + 2.0
  }
}

Define a class and function to implicitly use Parent Trait and to call the SECOND method.

class Utilizer2() {
  def utilize[T](x: T)(implicit trt: ParentTrait[T]): Unit = {
    println(trt.doSomething2(x))
  }
}

Call the method, specifying the type (Double). It DOES NOT pick up the Child Trait implicitly and instead throws an NotImplementedError. So it does find the doSomething2 method (there are no compilation errors) but it does not respect the hierarchy and uses the unimplemented form from the top level.

new Utilizer2().utilize[Double](1.0)
2

There are 2 best solutions below

0
On

You are performing aggregation instead of inheritance.

BetterParentTrait[T](trt: ParentTrait[T])

It means utilize method will not see your implicit class as it has different parent / interface. It has to implement ParentTrait class to be able implicitly passed to mentioned method.

implicit class BetterParentTrait[T] extends ParentTrait[T]{
  def doSomething(x: T): T = ???
  def doSomething2(x: T): T = ???
}
3
On

Does this help?

object MainUtilizers {

  trait ParentTrait[T] {
    def doSomething1(x: T): T
  }

  trait ChildTrait extends ParentTrait[Double] {
    override def doSomething1(x: Double): Double = {
      return x + 1.0
    }
  }


  trait BetterParentTrait[T] {
    def doSomething2(x: T): T = ???
  }

  trait  BetterChildTrait extends BetterParentTrait[Double] {
    override def doSomething2(x: Double): Double = {
      return x + 2.0
    }
  }

  //implicit object ChildTrait extends ChildTrait // Commented to avoid 2 implicits for ParentTrait[Double]

  implicit object BetterChildTrait extends BetterChildTrait with ChildTrait

  class Utilizer1() {
    def utilize[T](x: T)(implicit trt: ParentTrait[T]): Unit = {
      println(trt.doSomething1(x))
    }
  }
  class Utilizer2() {
    def utilize[T](x: T)(implicit trt: BetterParentTrait[T]): Unit = {
      println(trt.doSomething2(x))
    }
  }

  def main(args: Array[String]) {
    new Utilizer1().utilize[Double](1.0)

    new Utilizer2().utilize[Double](1.0)
  }
}