Type Class - Ambiguous Implicit Values Error

Hello!
I’m studying the type classes by going through the excellent manual “Essential Scala”. I’m doing one of the exercises and trying to write a Type Class that “should compare two values of type A and return a Boolean”; as an example I’m using a Person Class:

package language.typeclass.equivalence

case class Person(name: String, email: String)

so the T.C. is

package language.typeclass.equivalence

trait Equal[A] {
  def equal(a1: A, a2: A): Boolean
}

object Equal {
  def apply[A](implicit instance: Equal[A]): Equal[A] = instance
}

and the relative instances are:

object NameAndEmailEqual {
  implicit val nameAndEmailEqual: Equal[Person] =
    new Equal[Person] {
      def equal(p1: Person, p2: Person): Boolean =
        p1.name == p2.name && p1.email == p2.email
    }
}

object EmailEqual {
  implicit val emailEqual: Equal[Person] =
    new Equal[Person] {
      def equal(p1: Person, p2: Person): Boolean =
        p1.email == p2.email
    }
}

that is, one to compare two Persons by Email, and the other by Name and Email.
Now I enrich the type with the interface as follows:

object Eq {

  implicit class EqualEm[A](a1: A) {
    import EmailEqual._
    def isEqByEmail(a2: A)(implicit equal: Equal[A]): Boolean =
      equal.equal(a1, a2)
  }

  implicit class EqualNaE[A](p1: A) {
    import NameAndEmailEqual._
    def isEqByNameAndEmail(p2: A)(implicit equal: Equal[A]): Boolean = 
      equal.equal(p1, p2)
  }
}

But, when I try it on the REPL:

scala> import language.typeclass.equivalence.Person
import language.typeclass.equivalence.Person

scala> import language.typeclass.equivalence.Equal
import language.typeclass.equivalence.Equal

scala> import language.typeclass.equivalence.Equal._
import language.typeclass.equivalence.Equal._

scala> import language.typeclass.equivalence.Eq._
import language.typeclass.equivalence.Eq._

scala> val p1 = Person("Guido", "[email protected]")
p1: language.typeclass.equivalence.Person = Person(Guido,[email protected])

scala> val p2 = Person("Anna", "[email protected]")
p2: language.typeclass.equivalence.Person = Person(Anna,[email protected])

this is the result:

scala> p1.isEqByEmail(p2)
                     ^
       error: could not find implicit value for parameter equal: language.typeclass.equivalence.Equal[language.typeclass.equivalence.Person]

But I thought that I imported the implicits in scope by importing the relevant Instances in the single interfaces methods. OK. So I do:

scala> import language.typeclass.equivalence.EmailEqual._
import language.typeclass.equivalence.EmailEqual._

scala> p1.isEqByEmail(p2)
res1: Boolean = true

IT WORKS ( or so it seems). But I want use both extension, so I import the other instance:

scala> import language.typeclass.equivalence.NameAndEmailEqual._
import language.typeclass.equivalence.NameAndEmailEqual._

and now:

scala> p1 isEqByEmail p2
          ^
       error: ambiguous implicit values:
        both value emailEqual in object EmailEqual of type => language.typeclass.equivalence.Equal[language.typeclass.equivalence.Person]
        and value nameAndEmailEqual in object NameAndEmailEqual of type => language.typeclass.equivalence.Equal[language.typeclass.equivalence.Person]
        match expected type language.typeclass.equivalence.Equal[language.typeclass.equivalence.Person]

So, the question: how can avoid this ambiguity?
Thanks.

Basically, you’ve set up a situation that is ambiguous: you’ve got two equally-valid instances of the typeclass for your type. This is a bit unusual, but by no means rare.

When this happens, you have to distinguish at the call site which one is appropriate for your current circumstances. The way you’re doing it in your working example is one reasonable way to do it, but personally when this is looking like it’s happening, I don’t make the instances implicit in their definition – instead, I will typically leave then unimplicit, something like:

object EqualInstances {
  val nameAndEmailEqual: Equal[Person] =
    new Equal[Person] {
      def equal(p1: Person, p2: Person): Boolean =
        p1.name == p2.name && p1.email == p2.email
    }

  val emailEqual: Equal[Person] =
    new Equal[Person] {
      def equal(p1: Person, p2: Person): Boolean =
        p1.email == p2.email
    }

  implicit class EqualOps[T: Equal](t: T) {
    def equals(other: T): Boolean = implicitly[Equal[T]].equal(t, other)
  }
}
...
import EqualInstances._
...
implicit val desiredInstance = EmailEqual
p1 equals p2

Obviously, the implicit declaration doesn’t have to be right at the point of use – often it’ll be way up the call stack, and get passed down. But you generally have to say it explicitly; the compiler really doesn’t have good a way of distinguishing the two automatically…

1 Like