Tongfei Chen
Tongfei Chen

Reputation: 613

Enforcing precedence in implicit instances in Scala

This is a follow-up on the question Scala implicit typeclass precedence in companion objects.

Suppose that I have two traits, Trait2 extends Trait1. Each trait has a specific typeclass instance of Eq. I'd like to let the precedence of the typeclass instance of Trait2 to be higher than tha of Trait1. However, the code below (the LowPriorityImplicits trick) does not work.

trait Eq[-A] {
  def eq(a: A, b: A): Boolean
}

object Eq {
  implicit object IntEq extends Eq[Int] {
    def eq(a: Int, b: Int) = a == b
  }
}

trait Trait1[+A]
trait Trait2[+A] extends Trait1[A]

object Implicits extends LowPriorityImplicits {
  implicit def Eq2[T: Eq]: Eq[Trait2[T]] = ???
}

trait LowPriorityImplicits {
  implicit def Eq1[T: Eq]: Eq[Trait1[T]] = ???
}

object Test2 extends App {

  def f[T: Eq](x: T) = ???
  import Implicits._

  val t1 = new Trait1[Int] {}
  val t2 = new Trait2[Int] {}

  f(t2) // COMPILATION ERROR!

}

The following compilation error is thrown:

Error:(33, 4) ambiguous implicit values:
 both method Eq1 in trait LowPriorityImplicits of type [T](implicit evidence$2: Eq[T])Eq[Trait1[T]]
 and method Eq2 in object Implicits of type [T](implicit evidence$1: Eq[T])Eq[Trait2[T]]
 match expected type Eq[Trait2[Int]]
  f(t2)
   ^

How can I enforce the precedence relation of the typeclass instances?

Upvotes: 4

Views: 279

Answers (1)

codeFun
codeFun

Reputation: 41

Variance in type parameters doesn't play well with Scala's encoding of type classes. If you want it to compile, simply try this.

trait Eq[A] {
  def eq(a: A, b: A): Boolean
}

object Eq {
  implicit object IntEq extends Eq[Int] {
    def eq(a: Int, b: Int) = a == b
  }
}

trait Trait1[A]
trait Trait2[A] extends Trait1[A]

object Implicits extends LowPriorityImplicits {
  implicit def Eq2[T: Eq]: Eq[Trait2[T]] = ???
}

trait LowPriorityImplicits {
  implicit def Eq1[T: Eq]: Eq[Trait1[T]] = ???
}

object Test2 extends App {

  def f[T: Eq](x: T) = ???
  import Implicits._

  val t1 = new Trait1[Int] {}
  val t2 = new Trait2[Int] {}

  f(t2) // COMPILATION ERROR!

}

If you do want Eq[Trait2[A]] to behave like a sub-type of Eq[Trait1[A]], you might be able to use implicit conversions as a workaround.

Upvotes: 1

Related Questions