2015-04-27 1 views
2

J'ai lu TypeTag article connexe, mais je suis incapable de réaliser le filtrage d'une collection par type d'éléments.scala filtre par type

Exemple:

trait A 
class B extends A 
class C extends A 

val v = Vector(new B,new C) 
v filter (_.isInstanceOf[B]) 

Le code ci-dessus fonctionne bien. Cependant, je veux extraire filter sur v. Par exemple.

def filter[T,T2](data:Traversable[T2]) = (data filter ( _.isInstanceOf[T])).asInstanceOf[Traversable[T]] 

//Then filter v by 
filter[B,A](v) 

Dans ce cas, j'obtiens un avertissement abstract type T is unchecked since it is eliminated by erasure. J'ai essayé d'utiliser TypeTag, mais il ne semble pas facile d'obtenir Type à l'exécution.

Existe-t-il une solution élégante pour réaliser la fonction filter? Toute solution via macro macro est également acceptable.

Répondre

5

Vous devez fournir un ClassTag, pas un TypeTag, et utiliser la correspondance de motif. ClassTags fonctionnent bien avec la correspondance de modèle. Vous pouvez même utiliser la méthode collect pour effectuer la filter et map ensemble:

def filter[T, T2](data: Traversable[T2])(implicit ev: ClassTag[T]) = data collect { 
    case t: T => t 
} 

Par exemple:

val data = Seq(new B, new B, new C, new B) 
filter[B, A](data) //Traversable[B] with length 3 
filter[C, A](data) //Traversable[C] with length 1 

Un problème est qu'il pourrait ne pas fonctionner comme prévu avec les types génériques imbriqués.

La méthode collect prend en paramètre un paramètre de type PartialFunction, représentant une fonction qui n'a pas besoin d'être définie sur l'ensemble du domaine. Lorsque vous utilisez collect éléments où le PartialFunction est indéfini sont filtrés, et les éléments qui correspondent à une instruction case sont mappés en conséquence.

Vous pouvez également utiliser existential types et laisser le compilateur déduire le type du paramètre data pour une syntaxe plus concise. Vous pouvez également utiliser context bounds:

def filter[T : ClassTag](data: Traversable[_]) = data collect { case t: T => t } 
filter[B](data) 

Un problème avec les méthodes ici est qu'il ya une différence significative entre la méthode filter native vous avez: ces méthodes renvoie toujours un Traversable alors que le natif filter retourne le meilleur type il peut . Par exemple:

val data = Vector(new B, new B, new C, new B) 
data filter { _.isInstanceOf[B] } //Vector[A] 
data filter { _.isInstanceOf[B] } map { _.asInstanceOf[B] } //Vector[B] 
data collect { case t: B => t } //Vector[B]. Note that if you know the type at the calling point, this is pretty concise and might not need a helper method at all 

//As opposed to: 
filter[B](data) //Traversable[B], not a Vector! 

Vous pouvez résoudre ce problème en utilisant le modèle CanBuildFrom en utilisant un autre paramètre implicite. Vous pouvez également utiliser implicit classes pour ajouter essentiellement la méthode à la classe (par opposition à l'appel de la méthode dans le style statique indiqué ci-dessus).Tout cela ajoute à une méthode assez compliqué, mais je vais le laisser ici si vous êtes intéressé par ces améliorations:

implicit class RichTraversable[T2, Repr <: TraversableLike[T2, Repr], That](val trav: TraversableLike[T2, Repr]) extends AnyVal { 
    def withType[T : ClassTag](implicit bf: CanBuildFrom[Repr, T, That]) = trav.collect { 
     case t: T => t 
    } 
} 

Cela vous permettra de le faire:

data.withType[B] //Vector[B], as desired  
+0

Salut @BenReich, Merci pour votre belle réponse. Pourriez-vous nous en dire plus sur la syntaxe '{case t: B => t}' de 'data collect {cas t: B => t}'? Pourquoi n'avez-vous pas besoin de fournir une correspondance exhaustive? – worldterminator

+0

@worldterminator J'ai ajouté un petit edit expliquant 'collect' un peu plus. –

+0

@BenReich Merci, le dernier morceau de code est exactement ce dont j'avais besoin. C'est aussi un parfait exemple des possibilités de Scala et de la difficulté de son système de types et de sa bibliothèque de collections. J'espère pouvoir maîtriser des choses comme ça bientôt. – user42723

0

Il doit certainement y avoir un moyen plus facile. Les travaux ci-dessous, mais j'espère même une solution plus simple

trait A 
case class B() 
case class C() 
import scala.reflect.runtime.universe._ 
import scala.util.Try 
val m = runtimeMirror(getClass.getClassLoader) 
def filter[T](data:Traversable[_])(implicit t:TypeTag[T]) = data collect { 
     case i if Try(i.getClass.asSubclass(m.runtimeClass(typeOf[T].typeSymbol.asClass))).isSuccess => i.asInstanceOf[T] 
    } 
val v= Vector(new B,new C) 

scala>  println(filter[B](v)) 
Vector(B()) //This gets printed