tribbloid
tribbloid

Reputation: 3848

How to make implicits in an object preceding over another object?

Considering the following case:

  trait Companion {

    implicit def str(a: A): String =
      s"${this.getClass.getSimpleName}: %d" format a.n
  }

  class A(val n: Int)
  object A extends Companion {}

  class B(val x: Int, y: Int) extends A(y)
  object B extends Companion {}

Now compiling the following code will trigger a diverging implicit error:

val b = new B(5, 2)
val s: String = b
println(s)

because both object A and AA are in the default implicit scope of AA. This is clearly defective: the class AA is more 'exact' than trait A, henceforth it's implicit scope should have higher precedence. Unfortunately, because objects cannot inherit from each other, there is no way to declare this.

So my question is: what is the best way to achieve this, without resorting to non-default implicit scope?

Upvotes: 3

Views: 103

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51683

Now compiling the following code will trigger a diverging implicit error:

It's not "diverging implicit error", it's ambiguity, implicit ambiguity and implicit divergence are different.

Implicits regarding type X should go to companion object of X. So if this is an implicit conversion between A and String it should go to companion object of A. But then you have issue with .getSimpleName.

Common approach is parametrizing parent trait for companion objects (as @MarioGalic advises):

https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/generic/GenericCompanion.scala#L30

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeclass.scala#L44

#L84 #L150 #L178

If you don't want to make T a type parameter you can make it a type member

trait Companion {    
  type T <: A
  implicit def str(a: T): String = s"${this.getClass.getSimpleName}: %d" format a.n
}

class A(val n: Int)
object A extends Companion { 
  type T = A 
}

class B(val x: Int, y: Int) extends A(y)
object B extends Companion { 
  type T = B 
}

Also you can try to override implicit

trait Companion {
  implicit def str(a: A): String = s"${this.getClass.getSimpleName}: %d" format a.n
}

class A(val n: Int)
object A extends Companion 

class B(val x: Int, y: Int) extends A(y)
object B extends Companion {
  override implicit def str(a: A): String = super.str(a)
}

or

trait LowPriorityCompanion {
  implicit def str(a: A): String = s"${this.getClass.getSimpleName}: %d" format a.n
}

trait Companion extends LowPriorityCompanion {
  override implicit def str(a: A): String = super.str(a)
}

class A(val n: Int)
object A extends LowPriorityCompanion 

class B(val x: Int, y: Int) extends A(y)
object B extends Companion 

Upvotes: 2

Related Questions