Rado Buransky
Rado Buransky

Reputation: 3294

Get TypeTag[A] from Class[A]

I have createOld method that I need to override and I cannot change it. I would like to use TypeTag to pattern match provided type in createNew. The goal is to find out how to call createNew from createOld. My current understanding is that compiler doesn't have enough type information about A in createOld method if it doesn't already come with TypeTag[A].

object TypeTagFromClass {
  class C1
  class C2

  // How to get TypeTag[A] needed by createNew?
  def createOld[A](c: Class[A]): A = createNew ???

  def createNew[A : TypeTag]: A = {
    val result = typeOf[A] match {
      case a if a =:= typeOf[C1] => new C1()
      case a if a =:= typeOf[C2] => new C2()
    }
    result.asInstanceOf[A]
  }
}

Upvotes: 8

Views: 4048

Answers (2)

Vladimir Matveev
Vladimir Matveev

Reputation: 127721

It is possible to create a TypeTag from a Class using Scala reflection, though I'm not sure if this implementation of TypeCreator is absolutely correct:

import scala.reflect.runtime.universe._

def createOld[A](c: Class[A]): A = createNew {
  val mirror = runtimeMirror(c.getClassLoader)  // obtain runtime mirror
  val sym = mirror.staticClass(c.getName)  // obtain class symbol for `c`
  val tpe = sym.selfType  // obtain type object for `c`
  // create a type tag which contains above type object
  TypeTag(mirror, new TypeCreator {
    def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
      if (m eq mirror) tpe.asInstanceOf[U # Type]
      else throw new IllegalArgumentException(s"Type tag defined in $mirror cannot be migrated to other mirrors.")
  })    
}

However, you don't really need full TypeTag if you don't need to inspect generic parameters and full Scala type information. You can use ClassTags for that:

def createNew[A: ClassTag]: A = {
  val result = classTag[A].runtimeClass match {
    case a if a.isAssignableFrom(classOf[C1]) => new C1()
    case a if a.isAssignableFrom(classOf[C2]) => new C2()
  }
  result.asInstanceOf[A]
}

Or with some implicit sugar:

implicit class ClassTagOps[T](val classTag: ClassTag[T]) extends AnyVal {
  def <<:(other: ClassTag[_]) = classTag.runtimeClass.isAssignableFrom(other.runtimeClass)
}

def createNew[A: ClassTag]: A = {
  val result = classTag[A] match {
    case a if a <<: classTag[C1] => new C1()
    case a if a <<: classTag[C2] => new C2()
  }
  result.asInstanceOf[A]
}

You can simplify that even further by using plain old Java newInstance() method:

def createNew[A: ClassTag]: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]

This, of course, would only work if you don't need different constructor parameters for different classes.

Calling this createNew from createOld is much simpler than the one with TypeTags:

def createOld[A](c: Class[A]): A = createNew(ClassTag[A](c))

Upvotes: 10

DaunnC
DaunnC

Reputation: 1301

So it is not very safe and correct (cause you don't use the power of scala type system), but you can make (using your logic) to do the following:

def createNew[A](implicit t: TypeTag[A]): A = {
  val result: Any = t.tpe.toString match {
    case "C1" => new C1
    case "C2" => new C2
  }
  result.asInstanceOf[A]
}

createNew[C1] //> its all ok
createNew[C2] //> its all ok
createNew[C3] //> crashes here; lets pretend we got C3 class

To use it with createOld, just pass implicit argument:

def createOld[A](c: Class[A])(implicit t: TypeTag[A]): A = createNew[A]

createOld[C1] //> its all ok
createOld[C2] //> its all ok
createOld[C3] //> crashes here; lets pretend we got C3 class

I think I should not tell you twice that it is not very good. We can improve this code by using shapeless:

Lets create a poly function, which has a TypeTag as an argument:

 import shapeless._; import scala.reflect.runtime.universe._;

 def getTypeTag[T](implicit t: TypeTag[T]) = t //> to get TypeTag of a class

 // here is low prority implicit
 trait createPolyNewErr extends Poly1 {
   implicit def newErr[T] = at[T](_ => "Error can not create object of this class")
 } 

 object createPolyBew extends createPolyNewError {
   implicit def newC1 = at[TypeTag[C1]](_ => new C1)
   implicit def newC2 = at[TypeTag[C2]](_ => new C2)
 }

 createPolyNew(getTypeTag[C1]) //> success
 createPolyNew(getTypeTag[C2]) //> success
 createPolyNew(getTypeTag[C3]) //> String: Error can not create object of this class no crash!

We also can write a function, in order not to use function getTypeTag[T] every time:

 def createPoly[T]
 (implicit t: TypeTag[T],
         cse: poly.Case[createPolyNew.type, TypeTag[T] :: HNil]) = cse(t)

 createPoly[C1] //> its all ok
 createPoly[C2] //> its all ok
 createPoly[C3] //> String: Error can not create object of this class no crash!

Upvotes: 1

Related Questions