2014-05-03 6 views
5

Ho una collezione Scala che contiene oggetti di sottotipi diversi.In Scala come faccio a filtrare per tipi reificati in fase di esecuzione?

abstract class Base 

class A extends Base 

class B extends Base 

val a1 = new A() 
val a2 = new A() 
val b = new B() 
val s = List(a1, a2, b) 

mi piacerebbe per filtrare tutti gli oggetti o gli AB oggetti. Posso farlo facilmente se conosco l'oggetto su cui voglio filtrare in fase di compilazione.

s.filter(_.isInstanceOf[A]) // Give me all the As 
s.filter(_.isInstanceOf[B]) // Give me all the Bs 

Posso farlo se conosco solo il tipo di oggetto da filtrare in fase di esecuzione? Voglio scrivere una funzione come questa.

def filterType(xs:List[Base], t) = xs.filter(_.isInstanceOf[t]) 

Dove t indica se voglio oggetti di tipo A o B.

Ovviamente non riesco a scriverlo in questo modo a causa della cancellazione dei tipi. C'è un modo idiomatico di Scala per aggirare questo usando i tag tipo? Ho letto la documentazione dei tag di tipo Scala e i relativi post StackOverflow, ma non riesco a capirlo.

risposta

6

Questo è venuto fuori un paio di volte. Duplicato, chiunque?

scala> trait Base 
defined trait Base 

scala> case class A(i: Int) extends Base 
defined class A 

scala> case class B(i: Int) extends Base 
defined class B 

scala> val vs = List(A(1), B(2), A(3)) 
vs: List[Product with Serializable with Base] = List(A(1), B(2), A(3)) 

scala> def f[T: reflect.ClassTag](vs: List[Base]) = vs collect { case x: T => x } 
f: [T](vs: List[Base])(implicit evidence$1: scala.reflect.ClassTag[T])List[T] 

scala> f[A](vs) 
res0: List[A] = List(A(1), A(3)) 
+0

Un upvote per te, perché non mi rendevo conto che 'match' poteva sfruttare il' ClassTag', e questa è davvero la chiave qui. – wingedsubmariner

1

L'eliminazione dei tipi distrugge qualsiasi informazione nei parametri di tipo, ma gli oggetti sanno ancora a quale classe appartengono. Per questo motivo, non possiamo filtrare su tipi arbitrari, ma possiamo filtrare per classe o interfaccia/tratto. ClassTag è preferibile a TypeTag qui.

import scala.reflect.ClassTag 

def filterType[T: ClassTag](xs: List[Base]) = xs.collect { 
    case x: T => x 
} 

che possiamo usare come:

scala> filterType[B](s) 
res29: List[B] = List([email protected]) 

scala> filterType[Base](s) 
res30: List[Base] = List([email protected], [email protected], [email protected]) 

Questo metodo è sicuro in fase di esecuzione se il tipo T non è generica. Se ci fosse un class C[T] extends Base non potremmo filtrare in sicurezza su C[String].

+0

Sembra che @wingedsubmariner, essendo alato, mi ha appena battuto su di esso, anche se il pattern matcher ovvia al test isInstance. –

+0

Hai ragione, @ som-snytt. Ho ripulito la mia risposta per non usare più 'isInstance' e' asInstanceOf'. – wingedsubmariner

+0

@wingedsubmariner Questa è un'ottima soluzione; Ho provato ad aggiungere limiti di tipo alla firma della funzione sostituendo '[Base]' con '[_>: T]', l'idea è di catturare in fase di compilazione le operazioni di filtro che in linea di principio non potranno mai avere successo (assumendo l'elenco originale la popolazione non è stata indebolita). Tuttavia il compilatore non li rifiuta. Qualche idea su questo? – satyagraha

Problemi correlati