2014-05-03 5 views
5

Mam kolekcję Scala, która zawiera obiekty różnych podtypów.W programie Scala, jak filtrować według typów reifikowanych w środowisku wykonawczym?

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) 

chciałabym odfiltrować wszystkie A obiektów lub B obiektów. Mogę to zrobić z łatwością, jeśli znam obiekt, który chcę filtrować podczas kompilacji.

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

Czy mogę to zrobić, jeśli tylko znać typ obiektu, aby filtrować w czasie wykonywania? Chcę napisać taką funkcję.

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

Gdzie t wskazuje, czy chcę obiektów typu A lub B.

Oczywiście nie mogę tak napisać w ten sposób z powodu usunięcia typu. Czy istnieje idiomatyczna metoda Scala do obejścia tego przy użyciu znaczników typu? Czytałem dokumentację znaczników typu Scala i odpowiednie posty StackOverflow, ale nie mogę tego zrozumieć.

Odpowiedz

6

To pojawiło się kilka razy. Duplikować, ktoś?

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

Przeprowadzka dla ciebie, ponieważ nie zdawałem sobie sprawy, że 'match' może skorzystać z' ClassTag', i to naprawdę jest tutaj kluczowe. – wingedsubmariner

1

Usunięcie typu spowoduje zniszczenie dowolnej informacji w parametrach typu, ale obiekty będą nadal wiedzieć, do której klasy należą. Z tego powodu nie możemy filtrować według arbitralnych typów, ale możemy filtrować według klasy lub interfejsu/cechy. ClassTag jest lepszym rozwiązaniem niż TypeTag tutaj.

import scala.reflect.ClassTag 

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

której możemy używać jak:

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

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

Metoda ta jest bezpieczna w czasie wykonywania, jeśli typ T nie jest nazwą rodzajową. Jeśli byłby class C[T] extends Base, nie moglibyśmy bezpiecznie filtrować na C[String].

+0

Wygląda na to, że @wingedsubmariner, będąc skrzydlatym, po prostu mnie do niego pokonał, mimo że wzór matcher eliminuje test IsInstance. –

+0

Masz rację, @ ​​som-snytt. Oczyściłem moją odpowiedź, aby nie używać "isInstance" i "asInstanceOf". – wingedsubmariner

+0

@wingedsubmariner To jest świetne rozwiązanie; Próbowałem dodać ograniczenia typu do sygnatury funkcji, zastępując '[Base]' '[_>: T]', przy czym chodzi o to, aby przechwytywać podczas operacji filtrowania w czasie kompilacji, które w zasadzie nigdy nie mogą się powieść (zakładając, że oryginalna lista populacja nie została podważona). Jednak kompilator ich nie odrzuca. Jakieś pomysły na ten temat? – satyagraha

Powiązane problemy