Can I tell scala how to prefer more specific implicit, rather than give "ambiguous implicit" error?

StackOverflow https://stackoverflow.com/questions/19968114

  •  30-07-2022
  •  | 
  •  

Question

The following code gives an error.

class Base { }
class Sub extends Base { }

class Writer[-T] {
  def write(t: T) { }
}

object AmbImplicits {

  implicit object BaseWriter extends Writer[Base]
  implicit object SubWriter extends Writer[Sub]

  def foo[T](t: T)(implicit ev: Writer[T]) {
    println(s"foo $t $ev")
  }

  def main(args: Array[String]) {
    val base = new Base
    val sub = new Sub
    foo(base)
    foo(sub)
  }

}

The error:

/Workspace/AmbImplicits.scala:24: error: ambiguous implicit values:
both object SubWriter in object AmbImplicits of type AmbImplicits.SubWriter.type
and object BaseWriter in object AmbImplicits of type AmbImplicits.BaseWriter.type
match expected type Writer[Sub]
    foo(sub)
       ^

In my real code it is not so simple to explicitly pass the implicit parameter. Is there way to tell it to always prefer SubWriter over BaseWriter, because the former is more specific? Without manually passing it like foo(sub)(SubWriter)?

Was it helpful?

Solution

If you can't do what Shadowlands suggests due to Base and Sub being Java types, you can just reduce the priority of the BaseWriter implicit by moving it up the inheritance chain:

trait LowPriorityImplicits { implicit object BaseWriter extends Writer[Base] }

object AmbImplicits extends LowPriorityImplicits {
  implicit object SubWriter extends Writer[Sub]

  def foo[T](t: T)(implicit ev: Writer[T]) {
    println(s"foo $t $ev")
  }

  def main(args: Array[String]) {
    val base = new Base
    val sub = new Sub
    foo(base)
    foo(sub)
  }
}

OTHER TIPS

If you are able to, put the implicits in companion objects for each type derived from Base, rather than in the object with the foo method:

class Base { }
object Base { implicit object BaseWriter extends Writer[Base] }
class Sub extends Base { }
object Sub { implicit object SubWriter extends Writer[Sub] }

object NotAmbImplicits {

  def foo[T](t: T)(implicit ev: Writer[T]) {
    println(s"foo $t $ev")
  }

  def main(args: Array[String]) {
    val base = new Base
    val sub = new Sub
    foo(base)
    foo(sub)
  }

}

The compiler will look in the companion object for a given T passed to foo to see if it can use an implicit from there.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top