4 votes

Filtre de collection Scala par type

Je suis nouveau dans l'utilisation de Scala et j'ai rencontré le problème suivant :

Je veux obtenir une sous-collection d'une collection existante qui ne contient que des éléments d'un type spécifique. La méthode suivante fonctionne :

class C(val name : String)
class D(name : String) extends C(name) { }

val collection = Set[C](new C("C1"),new D("D1"),new C("C2"),new D("D2"))
collection.collect{case d : D => d}.size must be === 2 // works

Mais lorsque j'essaie d'étendre les classes de collection avec une méthode "onlyInstancesOf[Type]", cela ne fonctionne pas. D'abord mon implémentation :

object Collection {
    implicit def extendScalaCollection[E](coll : Traversable[E]) = new CollectionExtension[E](coll)
}

class CollectionExtension[E](coll : Traversable[E]) {

    def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
        coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
    }
}

Donc quand j'utilise cette extension et que j'exécute :

collection.onlyInstancesOf[D].size must be === 2

Je reçois un message d'erreur indiquant que .size a renvoyé 4 et non 2. De plus, j'ai vérifié, le résultat contient effectivement C1 et C2 alors qu'il ne devrait pas.

Quand je le fais :

collection.onlyInstancesOf[D].foreach(e => println(e.name))

Je comprends l'exception :

java.lang.ClassCastException: CollectionsSpec$$anonfun$1$C$1 cannot be cast to CollectionsSpec$$anonfun$1$D$1

Il est donc évident que l'ensemble résultant contient toujours les éléments qui auraient dû être filtrés.

Je ne comprends pas pourquoi cela se produit, quelqu'un peut-il l'expliquer ?

Edit : Scala : Scala code runner version 2.8.0.final

10voto

retronym Points 35066

Faites attention aux avertissements du compilateur, et ajoutez -unchecked dans vos options de ligne de commande scala.

M:\>scala -unchecked
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21)
.
Type in expressions to have them evaluated.
Type :help for more information.

scala> class CollectionExtension[E](coll : Traversable[E]) {
     |
     |     def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
     |         coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
     |     }
     | }
<console>:8: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
               coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
                                            ^
defined class CollectionExtension

L'avertissement signifie que le mieux que le compilateur puisse faire est équivalent à :

coll.collect({case special : AnyRef => special}).asInstanceOf[Traversable[_]]

Pour une explication plus détaillée de l'effacement des types, et des moyens de le contourner avec les manifestes, voir :

https://stackoverflow.com/questions/tagged/type-erasure+scala

6voto

Rex Kerr Points 94401

Comme d'autres l'ont souligné, les manifestes peuvent vous sauver. Voici un exemple de comment, en nous limitant aux non-primitives, et en supposant que nous ne voulons pas stocker les manifestes dans nos collections mais plutôt utiliser la réflexion sur place pour comprendre les choses :

class CollectionExtension[E <: AnyRef](coll : Traversable[E]) {
  def onlyInstancesOf[SpecialE <: E](implicit mf : Manifest[SpecialE]) : Traversable[SpecialE] = {
    coll.collect({
      case special if mf.erasure.isAssignableFrom(special.getClass) => special
    }).asInstanceOf[Traversable[SpecialE]]
  }
}

et le voici en action :

scala> val ce = new CollectionExtension(List(Some(1),Some(5),"This","Fox")) 
ce: CollectionExtension[java.lang.Object] = CollectionExtension@1b3d4787

scala> val opts = ce.onlyInstancesOf[Some[_]]
opts: Traversable[Some[_]] = List(Some(1), Some(5))

scala> val strings = ce.onlyInstancesOf[String] 
strings: Traversable[String] = List(This, Fox)

4voto

Submonoid Points 1817

Scala fonctionne sur la JVM, qui efface malheureusement les paramètres de type au moment de l'exécution : http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure . Dans votre premier exemple, vous donnez le type dans une position non effacée et le code d'exécution peut donc effectuer la comparaison. Dans le deuxième exemple, le SpecialE est effacé, et donc le code retournera tout.

Vous pouvez utiliser les manifestes de Scala pour récupérer certaines des informations perdues par l'effacement des types :

import scala.reflect.ClassManifest
class CollectionsExtension[E <: AnyRef](coll : Traversable[E]) {
  def onlyInstancesOf[SpecialE <: E](implicit m : Manifest[SpecialE]) : Traversable[SpecialE] = {
    coll.collect({case e if (ClassManifest.singleType(e) <:< m) => e}).asInstanceOf[Traversable[SpecialE]]
  }
}

3voto

Daniel C. Sobral Points 159554

Comme le disent les avertissements :

<console>:14: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
               coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]

Voyons l'implémentation de collect :

def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  val b = bf(repr)
  for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
  b.result
}

Notez qu'il n'y a pas de correspondance de motifs ici. C'est la différence fondamentale : lorsque vous écrivez " collection.collect{case d : D => d} "le compilateur sait exactement de quel type vous parlez : D .

D'autre part, lorsque vous écrivez coll.collect({case special : SpecialE => special}) le compilateur ne sait pas quel type SpecialE porque SpecialE est juste un paramètre de type. Donc, il ne peut pas générer de code qui sait ce qu'il faut faire. SpecialE est, et, au moment de l'exécution, il n'y a pas de SpecialE plus -- le bytecode utilise juste java.lang.Object .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X