Scala 的 (2.8) Manifest 是如何工作的?

Posted

技术标签:

【中文标题】Scala 的 (2.8) Manifest 是如何工作的?【英文标题】:How does Scala's (2.8) Manifest work? 【发布时间】:2010-08-27 18:57:52 【问题描述】:

我有一些 Scala 代码大量使用泛型,我从文档中了解到,在参数化约束中使用清单可以帮助我解决类型擦除问题(例如,我想实例化一个泛型类型)。只是,我想更多地了解这是如何工作的。它几乎感觉就像某种哈希图,它为每个调用站点获取一个条目......这里有人可以详细说明吗?

class Image[T <: Pixel[T] : Manifest](fun() => T, size: Array[Int], data: Array[T]) 
    def this(fun: () => T, size: Array[T]) 
        this(fun, size, new Array[T](size(0) * size(1));
    

这是我在网站上找到的任何文档中似乎都没有涉及的内容,在 Google 上,我大多会收到语法非常不同的旧帖子,而且因为 2.8 似乎有很多东西改变了,我不确定这些是否仍然准确。

【问题讨论】:

What is a Manifest in Scala and when do you need it? 的可能重复项 我不确定这些答案是否真正回答了它的工作原理?至少它可以更清楚。编译器是否向方法/函数添加了一个额外的参数来保存泛型参数的具体类? <:> 看看这里:debasishg.blogspot.com/2010/08/… 感谢您的链接;不过,它没有谈论清单。 【参考方案1】:

我已经有一段时间没有研究 Scala 的源代码以寻求回答同样的问题了......但我记得简短的答案 -

Manifest 是一个作弊码,允许编译器绕过类型擦除(它不在运行时使用)。它会导致在编译时为匹配清单的可能输入类型生成多个代码路径。

Manifest 被隐式解析,但如果在编译时对 Manifest 类型有任何歧义,编译器将停止。

有了Manifest 的副本,您就有了一些可用的东西。您通常想要的主要内容是通过erasure 删除的java.lang.Class

class BoundedManifest[T <: Any : Manifest](value: T) 
  val m = manifest[T]
  m.erasure.toString match 
    case "class java.lang.String" => println("String")
    case "double" | "int"  => println("Numeric value.")
    case x => println("WTF is a '%s'?".format(x))
    


class ImplicitManifest[T <: Any](value: T)(implicit m: Manifest[T]) 
  m.erasure.toString match 
    case "class java.lang.String" => println("String")
    case "double" | "int" => println("Numeric value.")
    case x => println("WTF is a '%s'?".format(x))
  


new BoundedManifest("Foo Bar!")
// String 
new BoundedManifest(5)
// Numeric value.
new BoundedManifest(5.2)
// Numeric value.
new BoundedManifest(BigDecimal("8.62234525"))
// WTF is a 'class scala.math.BigDecimal'?
new ImplicitManifest("Foo Bar!")
// String 
new ImplicitManifest(5)
// Numeric value.
new ImplicitManifest(5.2)
// Numeric value.
new ImplicitManifest(BigDecimal("8.62234525"))
// WTF is a 'class scala.math.BigDecimal'?

这是一个相当不稳定的例子,但显示了正在发生的事情。我在 Scala 2.8 上为输出以及 FWIW 运行了它。

[T ... : Manifest] 边界在 Scala 2.8 中是新的...您过去必须隐式获取清单,如 ImplicitManifest 所示。您实际上并没有获得清单的副本。但是您可以通过说 val m = manifest[T] ... manifest[_]Predef 上定义来在代码中获取一个,并且显然会在边界块内找到正确的清单类型。

您从Manifest 获得的另外两个主要项目是&lt;:&lt;&gt;:&gt;,它们测试一个清单与另一个清单的子类型/超类型。如果我没记错的话,这些都是非常幼稚的实现方式并且并不总是匹配,但是我有一堆生产代码使用它们来测试一些可能的已擦除输入。检查另一个清单的简单示例:

class BoundedManifestCheck[T <: Any : Manifest](value: T) 
  val m = manifest[T]
  if (m <:< manifest[AnyVal]) 
    println("AnyVal (primitive)")
   else if (m <:< manifest[AnyRef]) 
    println("AnyRef")
   else 
    println("Not sure what the base type of manifest '%s' is.".format(m.erasure))
  



new BoundedManifestCheck("Foo Bar!")
// AnyRef
new BoundedManifestCheck(5)
// AnyVal (primitive)
new BoundedManifestCheck(5.2)    
// AnyVal (primitive)
new BoundedManifestCheck(BigDecimal("8.62234525"))
// AnyRef

豪尔赫·奥尔蒂斯 (Jorge Ortiz) 有一篇很棒的博文(虽然很旧):http://www.scala-blogs.org/2008/10/manifests-reified-types.html

编辑

通过要求它打印擦除编译器阶段的结果,您实际上可以看到 Scala 正在做什么。

运行,在我上面的最后一个示例scala -Xprint:erasure test.scala 产生以下结果:

final class Main extends java.lang.Object with ScalaObject 
  def this(): object Main = 
    Main.super.this();
    ()
  ;
  def main(argv: Array[java.lang.String]): Unit = 
    val args: Array[java.lang.String] = argv;
    
      final class $anon extends java.lang.Object 
        def this(): anonymous class $anon = 
          $anon.super.this();
          ()
        ;
        class BoundedManifestCheck extends java.lang.Object with ScalaObject 
          <paramaccessor> private[this] val value: java.lang.Object = _;
          implicit <paramaccessor> private[this] val evidence$1: scala.reflect.Manifest = _;
          def this($outer: anonymous class $anon, value: java.lang.Object, evidence$1: scala.reflect.Manifest): BoundedManifestCheck = 
            BoundedManifestCheck.super.this();
            ()
          ;
          private[this] val m: scala.reflect.Manifest = scala.this.Predef.manifest(BoundedManifestCheck.this.evidence$1);
          <stable> <accessor> def m(): scala.reflect.Manifest = BoundedManifestCheck.this.m;
          if (BoundedManifestCheck.this.m().<:<(scala.this.Predef.manifest(reflect.this.Manifest.AnyVal())))
            scala.this.Predef.println("AnyVal (primitive)")
          else
            if (BoundedManifestCheck.this.m().<:<(scala.this.Predef.manifest(reflect.this.Manifest.Object())))
              scala.this.Predef.println("AnyRef")
            else
              scala.this.Predef.println(scala.this.Predef.augmentString("Not sure what the base type of manifest '%s' is.").format(scala.this.Predef.genericWrapArray(Array[java.lang.Object]BoundedManifestCheck.this.m().erasure())));
          protected <synthetic> <paramaccessor> val $outer: anonymous class $anon = _;
          <synthetic> <stable> def Main$$anon$BoundedManifestCheck$$$outer(): anonymous class $anon = BoundedManifestCheck.this.$outer
        ;
        new BoundedManifestCheck($anon.this, "Foo Bar!", reflect.this.Manifest.classType(classOf[java.lang.String]));
        new BoundedManifestCheck($anon.this, scala.Int.box(5), reflect.this.Manifest.Int());
        new BoundedManifestCheck($anon.this, scala.Double.box(5.2), reflect.this.Manifest.Double());
        new BoundedManifestCheck($anon.this, scala.package.BigDecimal().apply("8.62234525"), reflect.this.Manifest.classType(classOf[scala.math.BigDecimal]))
      ;
      
        new anonymous class $anon();
        ()
      
    
  

【讨论】:

另外值得注意的是,从最后一个示例中删除 : Manifest 会导致编译失败 - 当您调用 val m = manifest[T]error: could not find implicit value for parameter m: Manifest[T]。上下文边界提供了manifest[_] 调用需要操作的信息,因此它不是可选的。【参考方案2】:

“上下文绑定”T ... : Manifest 是隐式参数的语法糖:(implicit man: Manifest[T])。因此,在class Image 指定的类型构造函数的实例化点,编译器查找/提供用于类型参数T 的实际类型的Manifest,并且该值在其存在的整个过程中“坚持”生成的类实例,并且将 Image[Something] 的每个特定实例“锚定”到 Manifest 的 T

【讨论】:

谢谢,这有帮助,但我很想知道值如何与实例保持一致,即它的保存位置。 与任何构造函数参数相同(在构造函数外部引用):在字段中。 编辑了我的答案以包括擦除阶段的输出。实际上,您可以通过将 -Xprint:erasure 参数传递给编译器/运行时来让 Scalac 向您展示它正在生成的内容。这会在擦除阶段运行后打印出 Scala 代码的状态。

以上是关于Scala 的 (2.8) Manifest 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

将新的 Iterable 代码从 Scala 2.7.7 移植到 2.8

scala 2.8 中从 String 到 Int 的隐式转换

使用scala 2.8的maven和lift:lift-mapper丢失?

scala-创建泛型数组(T: Manifest)

<:<、<%< 和 =:= 在 Scala 2.8 中是啥意思,它们在哪里记录?

Scala类参数化中附加冒号的含义