Qu'est-ce qu'un manifeste dans Scala et quand en avez-vous besoin?

133

Depuis Scala 2.7.2, il y a quelque chose appelé Manifestqui est une solution de contournement pour l'effacement de type de Java. Mais comment Manifestfonctionne exactement et pourquoi / quand devez-vous l'utiliser?

Le billet de blog Manifests: Reified Types de Jorge Ortiz en explique une partie, mais n'explique pas comment l'utiliser avec les limites de contexte .

De plus, quelle est ClassManifestla différence avec Manifest?

J'ai du code (qui fait partie d'un programme plus large, je ne peux pas facilement l'inclure ici) qui contient quelques avertissements concernant l'effacement de type; Je suppose que je peux résoudre ces problèmes en utilisant des manifestes, mais je ne sais pas exactement comment.

Jesper
la source
2
Il y a eu une discussion sur la liste de diffusion à propos de la différence Manifest / ClassManifest, voir scala-programming-language.1934581.n4.nabble.com/…
Arjan Blokzijl

Réponses:

198

Le compilateur connaît plus d'informations sur les types que le runtime JVM ne peut facilement en représenter. Un manifeste est un moyen pour le compilateur d'envoyer un message interdimensionnel au code au moment de l'exécution sur les informations de type qui ont été perdues.

Ceci est similaire à la façon dont les Kleptoniens ont laissé des messages codés dans les archives fossiles et l'ADN «indésirable» des humains. En raison des limites de la vitesse de la lumière et des champs de résonance gravitationnelle, ils sont incapables de communiquer directement. Mais, si vous savez comment syntoniser leur signal, vous pouvez bénéficier d'une manière que vous ne pouvez pas imaginer, en décidant quoi manger pour le déjeuner ou quel numéro de loto jouer.

Il n'est pas clair si un manifeste bénéficierait des erreurs que vous voyez sans connaître plus de détails.

Une utilisation courante des Manifests consiste à faire en sorte que votre code se comporte différemment en fonction du type statique d'une collection. Par exemple, que se passe-t-il si vous souhaitez traiter une List [String] différemment des autres types de List:

 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

Une solution basée sur la réflexion impliquerait probablement d'inspecter chaque élément de la liste.

Un contexte lié semble le plus adapté à l'utilisation de classes de types dans scala, et est bien expliqué ici par Debasish Ghosh: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html

Les limites de contexte peuvent également rendre les signatures de méthode plus lisibles. Par exemple, la fonction ci-dessus pourrait être réécrite en utilisant des limites de contexte comme ceci:

  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")
  }
Mitch Blevins
la source
25

Pas une réponse complète, mais en ce qui concerne la différence entre Manifestet ClassManifest, vous pouvez trouver un exemple dans la Scala 2.8 Arraypapier :

La seule question qui reste est de savoir comment implémenter la création de tableaux génériques. Contrairement à Java, Scala permet la création d'une nouvelle instance Array[T]où se Ttrouve un paramètre de type. Comment cela peut-il être implémenté, étant donné qu'il n'existe pas de représentation de tableau uniforme en Java?

La seule façon de procéder consiste à exiger des informations d'exécution supplémentaires décrivant le type T. Scala 2.8 a un nouveau mécanisme pour cela, qui s'appelle un manifeste . Un objet de type Manifest[T]fournit des informations complètes sur le type T.
Manifestles valeurs sont généralement transmises dans des paramètres implicites; et le compilateur sait comment les construire pour des types statiquement connus T.

Il existe également une forme nommée plus faible ClassManifestqui peut être construite en ne connaissant que la classe de premier niveau d'un type, sans nécessairement connaître tous ses types d'arguments .
C'est ce type d'informations d'exécution qui est requis pour la création de tableau.

Exemple:

Il faut fournir ces informations en passant a ClassManifest[T]dans la méthode en tant que paramètre implicite:

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 
} 

En tant que forme abrégée, un contexte bound1 peut être utilisé sur le paramètre type à la Tplace,

(Voir cette question SO pour illustration )

, donnant:

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 
} 

Lors de l'appel de tabulate sur un type tel que Int, ou String, ou List[T], le compilateur Scala peut créer un manifeste de classe à passer comme argument implicite à tabulate.

VonC
la source
25

Un manifeste était destiné à réifier les types génériques qui sont effacés de type pour s'exécuter sur la JVM (qui ne prend pas en charge les génériques). Cependant, ils avaient de sérieux problèmes: ils étaient trop simplistes et étaient incapables de prendre en charge pleinement le système de types de Scala. Ils ont donc été déconseillés dans Scala 2.10, et sont remplacés par TypeTags (qui sont essentiellement ce que le compilateur Scala lui-même utilise pour représenter les types, et donc supporte pleinement les types Scala). Pour plus de détails sur la différence, consultez:

En d'autres termes

quand en as-tu besoin?

Avant le 04/01/2013, date de sortie de Scala 2.10 .

Escargot mécanique
la source
Ce n'est pas encore obsolète (mais le sera), car la réflexion Scala est encore expérimentale dans la version 2.10.
Keros
Avant le 04/01/2013, ou si vous utilisez une API qui en dépend.
David Moles
1

Voyons aussi manifestdans scalasources ( Manifest.scala), nous voyons:

Manifest.scala:
def manifest[T](implicit m: Manifest[T])           = m

Donc, en ce qui concerne l'exemple de code suivant:

def foo[A](somelist: List[A])(implicit m: Manifest[A]): String = {
  if (m <:< manifest[String]) {
    "its a string"
  } else {
    "its not a string"
  }
}

nous pouvons voir que la manifest functionrecherche d'un implicite m: Manifest[T]qui satisfait le que type parametervous fournissez dans notre exemple de code c'était manifest[String]. Donc, lorsque vous appelez quelque chose comme:

if (m <:< manifest[String]) {

vous vérifiez si le courant implicit mque vous avez défini dans votre fonction est de type manifest[String]et comme manifestest une fonction de type, manifest[T]il chercherait un spécifique manifest[String]et il trouverait s'il existe un tel implicite.

Tomer Ben David
la source