简体   繁体   中英

scala : Match type argument for an object

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 ?

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?

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

  1. 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 .

  2. 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...

  3. 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM