8

ケース クラスcopy()メソッドは、インスタンスの同一のコピーを作成し、フィールドを名前で置き換えることになっています。ケース クラスにマニフェストを含む型パラメーターがある場合、これは失敗するようです。コピーは、そのパラメーターの型に関するすべての情報を失います。

case class Foo[+A : Manifest](a: A) {
  // Capture manifest so we can observe it
  // A demonstration with collect would work equally well
  def myManifest = implicitly[Manifest[_ <: A]]
}

case class Bar[A <: Foo[Any]](foo: A) {
  // A simple copy of foo
  def fooCopy = foo.copy()
}

val foo = Foo(1)
val bar = Bar(foo)

println(bar.foo.myManifest)     // Prints "Int"
println(bar.fooCopy.myManifest) // Prints "Any"

パラメータのマニフェストが失われるのはなぜですか?Foo.copyまた、マニフェストを保持するにはどうすればよいですか?

4

2 に答える 2

15

いくつかの Scala の特性が相互作用して、この動作を実現します。まず、Manifests がコンストラクターの秘密の暗黙パラメーター リストに追加されるだけでなく、copy メソッドにも追加されることです。ことはよく知られている.

case class Foo[+A : Manifest](a: A)

は単なるシンタックスシュガーです

case class Foo[+A](a: A)(implicit m: Manifest[A])

ただし、これはコピー コンストラクターにも影響します。これは次のようになります。

def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)

これらすべてのimplicit ms はコンパイラによって作成され、暗黙的なパラメーター リストを介してメソッドに送信されます。

コンパイラが型パラメータcopyを知っている場所でメソッドを使用している限り、これは問題ありません。Fooたとえば、これは Bar クラスの外で機能します。

val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"

これが機能するのは、コンパイラがそれfooが であると推測し、それが であるFoo[Int]ことがわかっているfoo.aためIntですcopy

val aCopy = foo.copy()(manifest[Int]())

manifest[T]()(は、型のマニフェスト表現を作成する関数であることに注意してください。TたとえばManifest[T]、大文字の「M」を使用します。デフォルト パラメータの への追加は示されていません。)渡されたマニフェストが既にあるため、クラスcopy内でも機能します。Fooクラスが作成されたとき。次のようになります。

case class Foo[+A : Manifest](a: A) {
  def myManifest = implicitly[Manifest[_ <: A]]

  def localCopy = copy()
}

val foo = Foo(1)
println(foo.localCopy.myManifest) // Prints "Int"

ただし、元の例ではBar、2 番目の特殊性のためにクラスで失敗します。クラスBar内での型パラメーターは認識されBarていますが、型パラメーターの型パラメーターは認識されていません。AinBarが aFooまたは aSubFooまたはであることは認識していますが、 aまたはa である場合は認識してSubSubFooいません。もちろん、これは Scala でよく知られている型消去の問題ですが、クラスがs 型パラメーターの型で何もしていないように見える場合でも、ここでは問題として現れます。ただし、呼び出されるたびにマニフェストの秘密の注入があり、それらのマニフェストは以前にあったものを上書きすることを覚えておいてください。クラスにはわからないので、の型パラメーターでしたFoo[Int]Foo[String]foocopyBarfooつまり、次のようにマニフェストを作成してAny送信するだけです。

def fooCopy = foo.copy()(manifest[Any])

クラスを制御できる場合Foo(たとえば、 ではない場合List)、上記のように適切なコピーを実行しlocalCopy、結果を返すメソッドを追加して、Foo クラスですべてのコピーを実行することにより、1 つの回避策を実行します。

case class Bar[A <: Foo[Any]](foo: A) {
  //def fooCopy = foo.copy()
  def fooCopy = foo.localCopy
}

val bar = Bar(Foo(1))
println(bar.fooCopy.myManifest) // Prints "Int"

Foo別の解決策は、 s 型パラメーターを のマニフェスト型パラメーターとして追加することBarです。

case class Bar[A <: Foo[B], B : Manifest](foo: A) {
  def fooCopy = foo.copy()
}

ただし、クラス階層が大きい場合 (つまり、より多くのメンバーが型パラメーターを持ち、それらのクラスにも型パラメーターがある場合)、すべてのクラスがその下のすべてのクラスの型パラメーターを持つ必要があるため、これはうまくスケーリングしません。また、次のように構築しようとすると、型推論が狂ってしまうようBarです。

val bar = Bar(Foo(1)) // Does not compile

val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
于 2012-08-11T22:02:37.087 に答える
1

あなたが特定したように2つの問題があります。最初の問題は、のマニフェストの型BarBarわからない内の型消去の問題です。私はあなたが提案しFooた回避策を個人的に使用します。localCopy

2 番目の問題は、別の暗黙的なものが密かに に注入されていることcopyです。この問題は、明示的に値を再度 に渡すことで解決されますcopy。例えば:

scala> case class Foo[+A](a: A)(implicit val m: Manifest[A @uncheckedVariance])
defined class Foo

scala> case class Bar[A <: Foo[Any]](foo: A) {
     | def fooCopy = foo.copy()(foo.m)
     | }
defined class Bar

scala> val foo = Foo(1)
foo: Foo[Int] = Foo(1)

scala> val bar = Bar(foo)
bar: Bar[Foo[Int]] = Bar(Foo(1))

scala> bar.fooCopy.m
res2: Manifest[Any] = Int

コピーがIntマニフェストを保持していることがわかりますが、タイプはfooCopyres2Manifest[Any]消去によるものです。

これを行うには暗黙の証拠にアクセスする必要があったため、コンテキスト バインド構文ではなくcopy、明示的なimplicit(hah) 構文を使用する必要がありました。しかし、明示的な構文を使用するとエラーが発生しました。

scala> case class Foo[+A](a: A)(implicit val m: Manifest[A])
<console>:7: error: covariant type A occurs in invariant position in type => Manifest[A] of value m
       case class Foo[+A](a: A)(implicit val m: Manifest[A])
                                         ^
scala> case class Foo[+A](a: A)(implicit val m: Manifest[_ <: A])
defined class Foo

scala> val foo = Foo(1)
<console>:9: error: No Manifest available for Int.

なんてこと?コンテキストにバインドされた構文が機能し、明示的な構文が機能しないのimplicitはなぜですか? 私は掘り下げて、問題の解決策である@uncheckedVariance注釈を見つけました。

アップデート

さらに調べてみたところ、Scala 2.10 ではケース クラスが変更され、 の最初のパラメーター リストからフィールドのみcopy()をコピーするようになっていることがわかりました。

マーティンは次のように述べています: ケースクラス性は最初の引数リストにのみ付与され、残りはコピーされるべきではありません.

この変更の詳細については、https://issues.scala-lang.org/browse/SI-5009を参照してください。

于 2012-08-14T11:45:18.740 に答える