drh*_*gen 8 scala manifest type-erasure type-parameter
案例类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"
Run Code Online (Sandbox Code Playgroud)
为什么会Foo.copy
丢失参数上的清单以及如何保留它?
drh*_*gen 15
几个Scala特性相互作用以产生这种行为.第一件事是Manifest
s不仅附加到构造函数上的秘密隐式参数列表,还附加到复制方法.众所周知
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
s类型参数的地方使用该方法,这就没问题.例如,这将在Bar类之外工作:
val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为编译器推断它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"
Run Code Online (Sandbox Code Playgroud)
然而,在原始示例中,Bar
由于第二个特性,它在类中失败:虽然类中的类型参数Bar
是已知的Bar
,但类型参数的类型参数却不是.它知道A
in Bar
是a Foo
或a,SubFoo
或者SubSubFoo
不知道是a Foo[Int]
还是a Foo[String]
.当然,这是Scala中众所周知的类型擦除问题,但它在这里看起来是一个问题,即使它看起来似乎没有使用foo
s类型参数的类做任何事情.但是,记住,每次copy
调用时都会秘密注入一个清单,而那些清单会覆盖之前存在的清单.由于Bar
类不知道foo
is 的类型参数,它只是创建一个清单Any
并像这样发送:
def fooCopy = foo.copy()(manifest[Any])
如果一个人控制了这个Foo
类(例如它不是List
),那么通过添加一个能够进行正确复制的方法(localCopy
如上所述),通过在Foo类中进行所有复制来解决它,并返回结果:
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"
Run Code Online (Sandbox Code Playgroud)
另一种解决方案是添加Foo
s类型参数作为显式类型参数Bar
:
case class Bar[A <: Foo[B], B : Manifest](foo: A) {
def fooCopy = foo.copy()
}
Run Code Online (Sandbox Code Playgroud)
但是如果类层次结构很大,这种扩展性很差(即更多成员具有类型参数,并且这些类也具有类型参数),因为每个类都必须具有其下的每个类的类型参数.在尝试构建一个类似的推理时,它似乎也会使类型推断变得异常Bar
:
val bar = Bar(Foo(1)) // Does not compile
val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
Run Code Online (Sandbox Code Playgroud)