自Scala 2.7.2以来,有一些叫做Manifest
的东西,它是Java类型擦除的一种解决方法。但是Manifest
如何正确地工作以及为什么/何时需要使用它?
Jorge Ortiz撰写的博客文章 Manifests: Reified Types 解释了其中的一些内容,但未解释如何将其与context bounds一起使用。
此外,ClassManifest
是什么,与Manifest
的区别是什么?
我有一些代码(一个更大的程序的一部分,不能轻易地包含在这里),它有关于类型擦除的一些警告;我怀疑我可以通过使用清单解决这些问题,但我不确定究竟是怎么做的。
答案 0 :(得分:189)
编译器知道有关类型的更多信息,而不是JVM运行时可以轻松表示的信息。清单是编译器在运行时向代码发送关于丢失的类型信息的跨维消息的一种方式。
这类似于Kleptonians如何在化石记录和人类的“垃圾”DNA中留下编码信息。由于光速和重力场的限制,它们无法直接通信。但是,如果你知道如何调整他们的信号,你可以从你无法想象的方式中受益,从决定午餐吃什么或玩多少乐透号码。
目前尚不清楚Manifest是否会在不了解更多细节的情况下使您所看到的错误受益。
Manifest的一个常见用途是根据集合的静态类型使代码的行为不同。例如,如果您希望以不同于其他类型的List的方式处理List [String],那该怎么办?
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
if (m <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
foo(List("one", "two")) // Hey, this list is full of strings
foo(List(1, 2)) // Non-stringy list
foo(List("one", 2)) // Non-stringy list
基于反射的解决方案可能涉及检查列表中的每个元素。
上下文绑定似乎最适合在scala中使用类型类,Debasish Ghosh在这里对此进行了很好的解释: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html
上下文边界也可以使方法签名更具可读性。例如,可以使用如下的上下文边界重写上述函数:
def foo[T: Manifest](x: List[T]) = {
if (manifest[T] <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
答案 1 :(得分:25)
不是完整的答案,但关于Manifest
和ClassManifest
之间的区别,您可以在Scala 2.8 Array
paper中找到一个示例:
唯一剩下的问题是如何实现通用数组创建。与Java不同,Scala允许实例创建新
Array[T]
,其中T
是类型参数。考虑到Java中不存在统一的数组表示,如何实现它?执行此操作的唯一方法是需要额外的运行时信息来描述类型
T
。 Scala 2.8有一个新的机制,称为 Manifest 。Manifest[T]
类型的对象提供有关类型T
的完整信息Manifest
值通常在隐式参数中传递;并且编译器知道如何为静态已知类型T
构造它们。还存在一个名为
ClassManifest
的弱表单,它可以通过只知道一个类型的顶级类来构造,而不必知道它的所有参数类型。
正是这种类型的运行时信息是数组创建所必需的。
示例:
人们需要通过将
ClassManifest[T]
传递给我们来提供此信息 方法作为隐式参数:
def tabulate[T](len:Int, f:Int=>T)(implicit m:ClassManifest[T]) = {
val xs = new Array[T](len)
for (i <- 0 until len) xs(i) = f(i)
xs
}
作为简写形式,可以在类型参数
T
上使用上下文bound1,而不是
(见SO question for illustration)
,给予:
def tabulate[T: ClassManifest](len:Int, f:Int=>T) = {
val xs = new Array[T](len)
for (i <- 0 until len) xs(i) = f(i)
xs
}
在调用
Int
或String
或List[T]
等类型的表格时,Scala编译器可以创建一个类清单,作为隐式参数传递给表格。
答案 2 :(得分:23)
Manifest旨在重新定义在JVM上运行类型擦除的泛型类型(不支持泛型)。然而,他们有一些严重的问题:他们太简单了,并且无法完全支持Scala的类型系统。因此,它们在Scala 2.10中被弃用,并被TypeTag
替换(这实际上是Scala编译器本身用于表示类型的内容,因此完全支持Scala类型)。有关差异的更多详细信息,请参阅:
你什么时候需要它?
2013-01-04之前,when Scala 2.10 was released。
答案 3 :(得分:1)
让我们在manifest
来源(scala
)中查看Manifest.scala
,我们看到:
Manifest.scala:
def manifest[T](implicit m: Manifest[T]) = m
关于以下示例代码:
def foo[A](somelist: List[A])(implicit m: Manifest[A]): String = {
if (m <:< manifest[String]) {
"its a string"
} else {
"its not a string"
}
}
我们可以看到manifest
function
搜索了一个隐式m: Manifest[T]
,它满足您在我们的示例代码type parameter
中提供的manifest[String]
。所以,当你打电话时:
if (m <:< manifest[String]) {
您正在检查您在函数中定义的当前implicit m
是否为manifest[String]
类型,而manifest
是manifest[T]
类型的函数,它会搜索一个特定的manifest[String]
,它会发现是否存在这样的隐含。