Неоднозначные неявные значения
Я думал, что понимаю, что 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.