Comment fonctionne le Manifeste de Scala (2.8)?

J'ai un code Scala qui fait un usage assez lourd des génériques, et j'ai glané dans les docs que l'utilisation d'un manifeste dans les contraintes de paramétrisation peut m'aider à contourner les problèmes d'effacement de type (par exemple je veux instancier un nouvel objet du type générique). Seulement, j'aimerais mieux comprendre comment cela fonctionne. Il se sent presque comme une sorte de hashmap qui obtient une entrée pour chaque site d'invocation... Quelqu'un ici peut-il élaborer?

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

C'est quelque chose qui ne semble pas être couvert dans la documentation que j'ai trouvée sur le site, et sur Google, je reçois surtout des messages plus anciens qui ont une syntaxe très différente, et puisque 2.8 semble avoir beaucoup de choses changées, Je ne suis pas sûr que ceux-ci sont toujours précis.

23
demandé sur djc 2010-08-27 22:57:52

2 réponses

Cela fait un moment que je n'ai pas fouillé le code source de Scala dans une quête pour répondre à la même question... mais la réponse courte, si je me souviens -

Manifest est un code de triche pour permettre au compilateur de contourner l'effacement de Type (il n'est pas utilisé à l'exécution). Il provoque la génération de plusieurs chemins de code au moment de la compilation pour les types d'entrée possibles correspondant au manifeste.

Le Manifeste est résolu implicitement, mais s'il y a une ambiguïté au moment de la compilation sur ce que le Le type de manifeste est, le compilateur s'arrêtera.

, Avec une copie d'un Manifest, vous avez quelques choses à disposition. Les principales choses que vous voulez généralement est soit le {[4] } qui a été effacé via erasure:

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

C'est un exemple plutôt bancal mais qui montre ce qui se passe. J'ai couru cela pour la sortie aussi bien FWIW sur Scala 2.8.

La limite [T ... : Manifest] est nouvelle dans Scala 2.8... vous deviez saisir implicitement le manifeste comme indiqué dans ImplicitManifest. Vous N'obtenez pas une copie du Manifeste. Mais vous pouvez en chercher un dans votre code en disant val m = manifest[T] ... {[9] } est défini sur Predef et démontrable trouvera le type de manifeste approprié dans un bloc boundaried.

Les deux autres éléments principaux que vous obtenez d'un Manifest sont <:< et >:> qui testent le sous-type/supertype d'un manifeste par rapport à un autre. Si je me souviens bien, ce sont des implémentations très naïves et ne correspondent pas toujours, mais j'ai un tas de code de production qui les utilise pour tester quelques entrées effacées possibles. Simple exemple de vérification par rapport à un autre manifeste:

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 a un excellent billet de blog (quoique vieux) à ce sujet: http://www.scala-blogs.org/2008/10/manifests-reified-types.html

Modifier :

Vous pouvez réellement voir ce que Scala fait en lui demandant d'imprimer les résultats de la phase du compilateur d'effacement.

, dans mon dernier exemple ci-dessus scala -Xprint:erasure test.scala produit le résultat suivant:

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();
        ()
      }
    }
  }
}
35
répondu Brendan W. McAdams 2010-08-27 21:25:49

Le "contexte lié" T ... : Manifest est sucre syntaxique pour un argument implicite: (implicit man: Manifest[T]). Ainsi, au point d'instanciation du constructeur de type spécifié par class Image, le compilateur trouve / fournit le Manifeste pour le type réel utilisé pour le paramètre de type T et cette valeur "colle" avec l'instance de classe résultante tout au long de son existence et "ancre" chaque instance particulière de Image[Something] au Manifeste pour son T.

4
répondu Randall Schulz 2010-08-27 20:47:15