2010-08-27 5 views
23

Ich habe etwas Scala - Code, der ziemlich viel Generika verwendet, und ich habe aus den Dokumenten gelesen, dass die Verwendung eines Manifests in den Parametrisierungsbedingungen mir helfen kann, die Probleme beim Typlöschvorgang zu umgehen (zB ich möchte ein neues Objekt des generischer Typ). Ich würde nur gerne mehr darüber wissen, wie das funktioniert. Es fühlt sich fast an wie eine Art Hashmappe, die für jede Aufrufseite einen Eintrag bekommt ... Kann hier jemand etwas ausarbeiten?Wie funktioniert das 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)); 
    } 
} 

Dies ist etwas, das nicht in einem der Dokumentation abgedeckt zu sein scheint, dass ich auf der Website gefunden, und auf Google ich meist ältere Beiträge, die sehr unterschiedliche Syntax haben, und da 2.8 zu haben scheint Viele Dinge haben sich geändert, ich bin mir nicht sicher, ob diese noch richtig sind.

+0

möglich Duplikat (http://stackoverflow.com/ Fragen/3213510/Was-ist-ein-Manifest-in-Scala-und-wann-du-brauchst-es) –

+1

Ich bin nicht sicher, dass diese Antworten wirklich beantworten, wie es funktioniert, obwohl? Zumindest könnte es klarer sein. Ist der Punkt, dass der Compiler den Methoden/Funktionen ein zusätzliches Argument hinzufügt, um die konkrete Klasse für den generischen Parameter zu halten? Und was ist der <: djc

+0

Werfen Sie einen Blick hier: http://debasishg.blogspot.com/2010/08/using-generalized-type-constraints-how.html –

Antwort

35

Es ist eine Weile her, dass ich durch den Quellcode für Scala in einer Quest gegraben die gleiche Frage zu beantworten ... aber die kurze Antwort, wie ich mich erinnere -

Manifest ist ein Cheat-Code der Compiler zu ermöglichen umgehen Art löschen (es wird nicht zur Laufzeit verwendet). Es bewirkt, dass zur Kompilierungszeit mehrere Codepfade für die möglichen Eingabetypen generiert werden, die dem Manifest entsprechen.

Das Manifest wird implizit aufgelöst, aber wenn zur Kompilierungszeit Unklarheiten darüber bestehen, was der Manifest-Typ ist, wird der Compiler WILL stoppen.

Mit einer Kopie von Manifest haben Sie ein paar Dinge zur Verfügung. Die wichtigsten Dinge, die Sie normalerweise wollen, ist entweder die java.lang.Class, die über erasure gelöscht wurde:

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'? 

Dies ist ein ziemlich wackelig Beispiel zeigt aber, was los ist. Ich habe das für die Ausgabe auch FWIW auf Scala 2.8 ausgeführt.

Die [T ... : Manifest] Grenze ist neu in Scala 2.8 ... Sie mussten das Manifest implizit übernehmen, wie in ImplicitManifest gezeigt. Du erhältst tatsächlich keine Kopie des Manifests. Aber Sie können eine in Ihrem Code holen, indem Sie sagen: val m = manifest[T] ... ist definiert auf und wird nachweislich den richtigen Manifest-Typ in einem begrenzten Block finden.

Die beiden anderen wichtigen Elemente, die Sie bekommen von einem Manifest<:< und >:> welche Test Subtyp/geordneter Typ von einem Manifest gegen einen anderen. Wenn ich mich richtig erinnere, sind diese SEHR naiven Implementierungsweisen und stimmen nicht immer überein, aber ich habe eine Menge Produktionscode, der sie verwendet, um gegen einige mögliche gelöschte Eingaben zu testen.Ein einfaches Beispiel für die Überprüfung gegen ein anderes 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 

Jorge Ortiz hat eine große Blog-Post (wenn auch alt) dazu: http://www.scala-blogs.org/2008/10/manifests-reified-types.html

EDIT:

Sie können tatsächlich sehen, was Scala indem Sie es bitten, die Ergebnisse der Löschkompiliererphase auszudrucken.

Rennen, an meinem letzten Beispiel oben scala -Xprint:erasure test.scala ergibt folgendes Ergebnis: [? Was für ein Manifest in Scala ist und wann Sie sie brauchen]

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

Bemerkenswerterweise verursacht das Entfernen von ': Manifest' aus dem letzten Beispiel einen Kompilierungsfehler -' error: konnte keinen impliziten Wert für den Parameter m: Manifest [T] 'finden, wenn Sie' val m = manifest [T] 'aufrufen. Die Kontextgrenze stellt Informationen zur Verfügung, die der 'manifest [_]' Call benötigt, damit er nicht optional ist. –

4

Der "Kontext gebunden" T ... : Manifest ist syntaktischer Zucker für ein implizites Argument: (implicit man: Manifest[T]). Daher findet/liefert der Compiler zum Zeitpunkt der Instanziierung des durch class Image spezifizierten Typkonstruktors das Manifest für den tatsächlichen Typ, der für den Typparameter T verwendet wird, und dieser Wert "klebt" mit der resultierenden Klasseninstanz während ihrer gesamten Existenz und "Ankern" jeweils besondere Instanz von Image[Something] zum Manifest für seine T.

+0

Danke, das hilft, obwohl ich interessiert wäre zu wissen, wie der Wert mit der Instanz, d. H. Wo es gespeichert ist, bleibt. – djc

+0

Identisch mit jedem Konstruktorparameter (der außerhalb des Konstruktors referenziert wird): In einem Feld. –

+0

bearbeitet meine Antwort, um die Ausgabe der Löschphase einzuschließen. Sie können tatsächlich Scalac zeigen lassen, was es erzeugt, indem Sie das Argument "-Xprint: löscht" an den Compiler/Runtime übergeben. Dies gibt den Zustand Ihres Scala-Codes nach Ablauf der Löschphase aus. –