Reputation: 2151
if i have a class that accepts a Type argument for example Seq[T]
, and i've many objects of this class. and i want to split them depending on type Argument T
for example :
val x = List(Seq[Int](1,2,3,4,5,6,7,8,9,0),Seq[String]("a","b","c"))
x.foreach { a =>
a match{
case _ : Seq[String] => print("String")
case _ : Seq[Int] => print("Int")
}
}
the result of this code is StringString
.
it only matches the class Seq
not the Type also , what should i do to force it to match the Type ?
Upvotes: 3
Views: 4938
Reputation: 13686
I would argue it's equally sensible to def
the logic with multiple prototypes one per sequence type, than go into those type-erasure workarounds. The 2.10 compiler doesn't warn about type erasure, and at runtime it seems to work well in my case.
Presumably this avoids the problem, producing more intelligible code.
Upvotes: 0
Reputation: 23105
TypeTag Approach
The Java runtime requires generic type param erasure. Scala compiler combats this by 'injecting' type info into methods declared with TypeTag
type arg:
def typeAwareMethod[T: TypeTag] (someArg: T) {
... // logic referring to T, the type of varToCheck
}
(alternatively, can use an equivalent, more long-winded implicit param - not shown)
When scala compiles an invocation of a method having (1) type arg [T: TypeTag]
and (2) normal arg someArg: T
, it collects type param metadata for someArg
from the calling context and augments the type arg T
with this metadata. T's value plus tag data are externaly type-inferred from calls:
val slimesters = List[Reptile](new Frog(...), new CreatureFromBlackLagoon(...))
typeAwareMethod(slimesters)
Logic Referring to T (within above method) - runtime reflection
import scala.reflection.runtime.universe._
: contents of the universe object of type scala.relection.api.JavaUniverse
. NB: subject to evolutionary API change
Direct TypeTag
comparison:
The tag info can be obtained via method typeTag[T]
, then directly tested/pattern matched for (exact) equality with other type tags:
val tag: TypeTag[T] = typeTag[T]
if (typeTag[T] == typeTag[List[Reptile]]) ...
typeTag[T] match {
case typeTag[List[Reptile]] => ...
}
Limitations: not subtype aware (above won't match List[Frog]
); no additional metadata obtainable through TypeTag
.
Smarter Type-comparison operations:
Convert to Type
via typeOf[T]
(or typeTag[T].tpe
). Then use the gammut of Type
ops, including pattern-matching. NB: in reflection typespace, =:=
means type equivalance (analogue of :
), <:<
means type conformance (analogue of <:
)
val tType: Type = typeOf[T] // or equivalently, typeTag[T].tpe
if (typeOf[T] <:< typeOf[List[Reptile]]) ... // matches List[Frog]
typeOf[T] match {
case t if t <:< typeOf[List[Reptile]] => ...
}
// Running Example:
def testTypeMatch[T: TypeTag](t: T) = if (typeOf[T] <:< typeOf[Seq[Int]]) "yep!!!"
test(List[Int](1, 2, 3)) // prints yep!!!
Method still needs type param [T: TypeTag] or you'll get the type-erasure view of the world...
Introspect on Type metadata
I lied in 2 ;). For your case, typeOf[T]
actually returns TypeRef
(a subtype of Type
), since you're instantiating a type declared elsewhere. To get at the full metadata, you need to convert Type
to TypeRef
.
typeTag[T].tpe match {
case t: TypeRef => ... // call t.args to access typeArgs (as List[Type])
case _ => throw IllegalArgumentException("Not a TypeRef")
}
instead of t: TypeRef
, can extract parts via pattern match on:
case TypeRef(prefixType, typeSymbol, typeArgsListOfType) =>
Type
has method:
def typeSymbol: Symbol
Symbol has methods:
def fullName: String
def name: Name
Name has methods:
def decoded: String // the scala name
def encoded: String // the java name
Solution For Your Case
Solution based on (3):
import scala.reflect.runtime.universe._
def typeArgsOf[T: TypeTag](a: T): List[Type] = typeOf[T] match {
case TypeRef(_, _, args) => args
case _ => Nil
}
val a = Seq[Int](1,2,3,4,5,6,7,8,9,0)
val b = Seq[String]("a","b","c")
// mkString & pring for debugging - parsing logic should use args, not strings!
print("[" + (typeArgsOf(a) mkString ",") + "]")
print("[" + (typeArgsOf(b) mkString ",") + "]")
Aside: there's an issue with this test case:
val x = List(Seq[Int](1,2,3,4,5,6,7,8,9,0),Seq[String]("a","b","c"))
Type of x is List[Seq[Any]]. Any is the lowest common ancestor of String and Int. In this case there's nothing to introspect, since all types descend from Any , and there's no further type information available. To get stronger typing, separate the two Seqs, either via separate variables or a tuple/pair - but once separated, no higher order common mapping / folding across the two. "Real world" cases shouldn't have this problem.
Upvotes: 5
Reputation: 6331
What you're seeing happens due to Type Erasure (http://docs.oracle.com/javase/tutorial/java/generics/erasure.html), some IDEs can warn you for errors like these.
You could have a look at Manifests, for example check out What is a Manifest in Scala and when do you need it?
Edit: like Patryk said, TypeTag replaced Manifest in Scala 2.10, see Scala: What is a TypeTag and how do I use it?
Upvotes: 5