几个 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
s 都由编译器创建并通过隐式参数列表发送到方法。
只要在编译器知道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
,但类型参数的类型参数却不知道。它知道A
inBar
是 aFoo
或 a SubFoo
or SubSubFoo
,但不知道 in 是 aFoo[Int]
或 a Foo[String]
。这当然是 Scala 中众所周知的类型擦除问题,但即使看起来类似乎没有对foo
s 类型参数的类型做任何事情,它也会在这里出现问题。但它是,记住每次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"
另一种解决方案是将Foo
s 类型参数添加为 的显示类型参数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