web-dev-qa-db-ja.com

あいまいな暗黙の値

最近奇妙な問題に直面するまで、私はscala暗黙的)を理解していると思っていました。

私のアプリケーションにはいくつかのドメインクラスがあります

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]

そして、文字列をドメインクラスの1つに変換するヘルパー

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を使用しますが、この簡略化されたバージョンでエラーを再現できます。

19
lambdas

あなたの例で遭遇している曖昧さは、あなたがどちらを使いたいかを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)

BarBazの両方に定義されているものがあると仮定して、どの暗黙的に使用しますか?そこにはもっと悪魔のようなコーナーケースがあると確信していますが、これは私に飛び出します。

13
wheaties

問題は、ジェネリック型Aを不変にすることです。代わりに、これは共変である必要があります。したがって、これは次のように実装する必要があります

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)

詳細については、 http://docs.scala-lang.org/tutorials/tour/variances.html を参照してください。

8
user5551927