22
votes

I've been thinking I understand scala implicits until recently faced strange problem.

In my application I have several domain classes

case class Foo(baz: String)
case class Bar(baz: String)

And a class that is able to construct domain object from a string. It could be subclassed to do real deserialization it doesn't matter.

class Reads[A] {
  def read(s: String): A = throw new Exception("not implemented")
}

Next, there are implicit deserializers

implicit val fooReads = new Reads[Foo]
implicit val barReads = new Reads[Bar]

And a helper to convert strings to one of domain classes

def convert[A](s: String)(implicit reads: Reads[A]): A = reads.read(s)

Unfortunatelly, when trying to use it

def f(s: String): Foo = convert(s)

I get compiler errors like

error: ambiguous implicit values:
 both value fooReads of type => Reads[Foo]
 and value barReads of type => Reads[Bar]
 match expected type Reads[A]
       def f(s: String): Foo = convert(s)
                                      ^

To me code seems simple and right. Reads[Foo] and Reads[Bar] is a completely different types, what is ambiguous about it?

The real code is much more complicated and uses play.api.libs.json but this simplified version is sufficient to reproduce the error.

2

2 Answers

14
votes

The ambiguity you're encountering in your example is that you didn't tell Scalac which one you wanted to use. You need to replace your code with

def f(s: String): Foo = convert[Foo](s)

in order for it to figure out which one to use. It can't infer from the return type of f. It needs to be explicit here.

Response to comment

Let me play devil's advocate here.

trait Foo
case class Bar(s: String) extends Foo
case class Baz(s: String) extends Foo

def f(s: String): Foo = convert(s)

which implicit does it use assuming there is one defined for both Bar and Baz? I'm sure there's more devilish corner cases out there but this one jumps out to me.

10
votes

The problem is that you are making generic type A invariant. Instead this should be covariant. So this should be implemented as follows

case class Foo(baz: String)
case class Bar(baz: String)

// This `+` ↓ is the only difference  
class Reads[+A] {
  def read(s: String): A = throw new Exception("not implemented")
}

implicit val fooReads = new Reads[Foo]
implicit val barReads = new Reads[Bar]

def convert[A](s: String)(implicit reads: Reads[A]): A = reads.read(s)

def f(s: String): Foo = convert(s)

Please refer to http://docs.scala-lang.org/tutorials/tour/variances.html for more information.