Неоднозначные неявные значения

Я думал, что понимаю, что scala implicits до недавнего времени сталкивался с какой-то странной проблемой.

В моем приложении у меня есть несколько классов домена

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

И класс, способный построить объект домена из строки. Это может быть подклассифицировано для реальной десериализации, это не имеет значения.

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)

Я получаю ошибки компилятора, такие как

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)
                                      ^

Мне код кажется простым и правильным. Reads[Foo] и Reads[Bar] - это совершенно разные типы, что в этом двусмысленно?

Реальный код намного сложнее и использует play.api.libs.json, но этой упрощенной версии достаточно, чтобы воспроизвести ошибку.

Ответы

Ответ 1

Неопределенность, с которой вы сталкиваетесь в своем примере, заключается в том, что вы не сказали Scalac, какой из них вы хотели использовать. Вам нужно заменить код

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

чтобы выяснить, какой из них использовать. Он не может вывести из возвращаемого типа f. Он должен быть здесь явным.

Ответ на комментарий

Позвольте мне играть здесь адвоката дьявола.

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

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

который подразумевает, что он использует предположение, что существует один, определенный как для Bar, так и для Baz? Я уверен, что там более дьявольские угловые случаи, но этот один выпрыгивает ко мне.

Ответ 2

Проблема в том, что вы делаете универсальный тип A инвариантом. Вместо этого это должно быть ковариантным. Поэтому это должно быть выполнено следующим образом

case class Foo(baz: String)
case class Bar(baz: String)
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)

Подробнее см. http://docs.scala-lang.org/tutorials/tour/variances.html.