案例类copy()
方法应该创建实例的相同副本,并且按名称替换任何字段。当case类具有带有清单的类型参数时,这似乎失败了。该副本失去了对其参数类型的所有知识。
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
会丢失参数上的清单以及如何保留它?
答案 0 :(得分:15)
几种Scala特性相互作用以产生这种行为。第一件事是Manifest
不仅附加到构造函数上的秘密隐式参数列表,还附加到复制方法。众所周知,
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 m
都是由编译器创建的,并通过隐式参数列表发送给方法。
只要在编译器知道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
类中失败:Bar
类中的Bar
类型参数已知,类型参数类型参数不是。它知道A
中的Bar
是Foo
或SubFoo
或SubSubFoo
,但如果它是Foo[Int]
或{{{} 1}}。当然,这是Scala中众所周知的类型擦除问题,但它似乎是一个问题,即使它看起来似乎没有使用Foo[String]
类型参数的类做任何事情。但是,记住,每次调用foo
时都会秘密注入一个清单,而这些清单会覆盖之前存在的清单。由于copy
类不知道Bar
的类型参数是什么,它只是创建了foo
的清单,并像这样发送:
Any
如果一个人可以控制def fooCopy = foo.copy()(manifest[Any])
类(例如它不是Foo
),那么通过添加一个可以进行正确复制的方法,在Foo类中进行所有复制,可以解决这个问题。如上面的List
,并返回结果:
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"
s类型参数作为Foo
的显式类型参数:
Bar
但是如果类层次结构很大,那么这个扩展性很差(即,更多成员具有类型参数,并且这些类也具有类型参数),因为每个类都必须具有其下面的每个类的类型参数。在尝试构建case class Bar[A <: Foo[B], B : Manifest](foo: A) {
def fooCopy = foo.copy()
}
:
Bar
答案 1 :(得分:1)
您发现有两个问题。第一个问题是Bar
内部的类型擦除问题,其中Bar
不知道Foo
清单的类型。我个人会使用您建议的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
清单,但由于删除,fooCopy
和res2
的类型为Manifest[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.
WTF?为什么上下文绑定语法有效,而显式implicit
则不然?我挖出来找到问题的解决方案:@uncheckedVariance
注释。
<强>更新强>
我挖了更多,发现在Scala 2.10中,案例类已更改为仅复制copy()
中第一个参数列表中的字段。
马丁说:案件类别仅限于第一个论点 列表其余部分不应复制。
请在https://issues.scala-lang.org/browse/SI-5009查看此更改的详细信息。