2010-08-27 61 views
23

我有一些Scala代碼,使相當大量的泛型使用,我從文檔中收集在參數化約束中使用清單可以幫助我解決類型擦除問題(例如,我想實例化一個新的對象泛型類型)。只是,我想了解更多關於這是如何工作的。它幾乎感覺就像某種散列表,它爲每個調用網站獲得一個條目......任何人都可以在這裏詳細說明嗎?Scala的(2.8)Manifest如何工作?

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)); 
    } 
} 

這件事似乎沒有任何,我在網站上發現的文件被覆蓋,並在谷歌我大部分得到具有非常不同的語法舊帖子,並自2.8似乎有很多事情發生了變化,我不確定這些事情是否準確。

+0

可能重複(http://stackoverflow.com/問題/ 3213510 /什麼是一個清單在scala和什麼時候你需要它) – 2010-08-27 19:38:35

+1

我不確定那些答案真的回答它的工作原理,但?至少它可以更清楚。編譯器爲方法/函數添加一個額外參數以保存泛型參數的具體類的意義何在?無論如何,<:<運算符是什麼? – djc 2010-08-27 19:42:55

+0

看看這裏:http://debasishg.blogspot.com/2010/08/using-generalized-type-constraints-how.html – 2010-08-27 19:47:31

回答

35

它已經一段時間,因爲我經歷了Scala的源代碼在尋求回答同樣的問題......但簡短的回答挖,我記得 -

清單是個騙子代碼,讓編譯器繞過類型擦除(它不是在運行時使用)。它會在編譯時爲匹配清單的可能輸入類型生成多個代碼路徑。

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所示。你實際上並沒有獲得Manifest的副本。但是,您可以通過說val m = manifest[T] ... manifest[_]Predef上定義,並且可以在限制塊內找到正確的清單類型,從而在代碼中獲取一個。

您從Manifest獲得的另外兩個主要項目是<:<>:>,它測試一個清單的子類型/超類型與另一個清單。如果我正確記得這些是非常天真的執行方式,並不總是匹配,但我有一堆生產代碼使用它們來測試一些可能的擦除輸入。

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 

豪爾赫·奧爾蒂斯有一個偉大的博客文章(雖然是舊)本:http://www.scala-blogs.org/2008/10/manifests-reified-types.html

編輯:對另一清單檢查的一個簡單的例子

實際上,你可以看到Scala是什麼通過要求它打印出擦除編譯器階段的結果。

運行,在我的最後一個例子以上scala -Xprint:erasure test.scala產生以下結果:?什麼是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(); 
     () 
     } 
    } 
    } 
} 
+1

另外值得注意的是,從最後一個例子中刪除':Manifest'會導致編譯失敗 - 錯誤:當您調用val m = manifest [T]'時,找不到參數m:Manifest [T]的隱式值。上下文邊界提供'manifest [_]'調用需要操作的信息,所以它不是可選的。 – 2010-08-27 23:17:29

4

「上下文綁定」T ... : Manifest是隱含參數的語法糖:(implicit man: Manifest[T])。因此,在由class Image指定的類型構造函數的實例化處,編譯器查找/提供用於類型參數T的實際類型的Manifest,並且該值「堅持」整個存在的結果類實例和每個「錨」 Image[Something]的特定實例爲其清單T

+0

謝謝,這有幫助,但我會有興趣知道價值如何與實例掛鉤,即它保存的位置。 – djc 2010-08-27 21:17:00

+0

與任何構造函數參數(在構造函數外引用)相同:在一個字段中。 – 2010-08-27 21:24:05

+0

編輯我的答案,包括擦除階段的輸出。實際上你可以讓Scalac向編譯器/運行時傳遞'-Xprint:erasure'參數來展示它的生成過程。這將在擦除階段運行後打印出您的Scala代碼的狀態。 – 2010-08-27 21:27:41