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 不仅附加到构造函数的秘密隐式参数列表中,而且还附加到复制方法上。众所周知,

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 都由编译器创建并通过隐式参数列表发送到方法。

只要在编译器知道s 类型参数copy的地方使用该方法就可以了。Foo例如,这将在 Bar 类之外工作:

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

这是有效的,因为编译器推断这foo是 aFoo[Int]所以它知道这foo.a是 aInt所以它可以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由于第二个特性,它在类中失败了:虽然 的类型参数Bar在类中是已知的Bar,但类型参数的类型参数却不知道。它知道AinBar是 aFoo或 a SubFooor SubSubFoo,但不知道 in 是 aFoo[Int]或 a Foo[String]。这当然是 Scala 中众所周知的类型擦除问题,但即使看起来类似乎没有对foos 类型参数的类型做任何事情,它也会在这里出现问题。但它是,记住每次copy调用时都会秘密注入清单,这些清单会覆盖之前存在的清单。由于Bar该类不知道类型参数是foo是,它只是创建一个清单Any并将其发送如下:

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

如果一个人可以控制Foo该类(例如它不是List),那么一个解决方法是通过添加一个将执行正确复制的方法在 Foo 类中完成所有复制,就像localCopy上面一样,并返回结果:

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"

另一种解决方案是将Foos 类型参数添加为 的显示类型参数Bar

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

但是,如果类层次结构很大(即更多成员具有类型参数,并且这些类也具有类型参数),则这种扩展性很差,因为每个类都必须具有其下每个类的类型参数。在尝试构造 a 时,它似乎也使类型推断变得异常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

您发现有两个问题。第一个问题是里面的类型擦除问题BarBar不知道Foo's manifest 的类型。我会亲自使用localCopy您建议的解决方法。

第二个问题是另一个隐式被偷偷注入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().

Martin 说:case class ness 只赋予第一个参数列表,其余的不应该被复制。

在https://issues.scala-lang.org/browse/SI-5009中查看此更改的详细信息。

于 2012-08-14T11:45:18.740 回答