Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic instantiation in Scala using TypeTag and ClassTag

In Scala 2.9 one could implement polymorphic instantiation as

def newInstance[T](implicit m: Manifest[T]) =
    m.erasure.newInstance.asInstanceOf[T]

but as of 2.10 Manifest is being replaced with TypeTag, and it is not clear to me how to achieve something similar with TypeTag. I would prefer if the TypeTag version preserved all available type information.

I know that the above only works for traits/classes that do not require constructor args, and ven then it does not always work, but it works well enough for what I need. If I can do better the new reflection APIs that would be great.

like image 377
Daniel Mahler Avatar asked Aug 28 '13 22:08

Daniel Mahler


2 Answers

TypeTag is not yet a replacement for Manifest because it's a part of experimental and unstable Scala reflection. You definitely shouldn't use it for production as of now.

For the use case you showed, where only runtime class is needed (not full type information with generics etc.), Scala 2.10 introduced ClassTag, which you can use like this:

def newInstance[T: ClassTag] =
  implicitly[ClassTag[T]].runtimeClass.newInstance.asInstanceOf[T]

or:

def newInstance[T](implicit ct: ClassTag[T]) =
  ct.runtimeClass.newInstance.asInstanceOf[T]

Anyway, Manifest isn't deprecated yet, so I guess you can still use it.

EDIT:

Using TypeTag to achieve the same:

import scala.reflect.runtime.universe._

def newInstance[T: TypeTag] = {
  val clazz = typeTag[T].mirror.runtimeClass(typeOf[T])
  clazz.newInstance.asInstanceOf[T]
}

The above solution still uses some Java reflection. If we want to be puristic and use only Scala reflection, this is the solution:

def newInstance[T: TypeTag]: T = {
  val tpe = typeOf[T]

  def fail = throw new IllegalArgumentException(s"Cannot instantiate $tpe")

  val noArgConstructor = tpe.member(nme.CONSTRUCTOR) match {
    case symbol: TermSymbol =>
      symbol.alternatives.collectFirst {
        case constr: MethodSymbol if constr.paramss == Nil || constr.paramss == List(Nil) => constr
      } getOrElse fail

    case NoSymbol => fail
  }
  val classMirror = typeTag[T].mirror.reflectClass(tpe.typeSymbol.asClass)
  classMirror.reflectConstructor(noArgConstructor).apply().asInstanceOf[T]
}
like image 155
ghik Avatar answered Oct 09 '22 08:10

ghik


If you want to support passing args, here's a trick I do with 2.11:

def newInstance[T : ClassTag](init_args: AnyRef*): T = {
classTag[T].runtimeClass.getConstructors.head.newInstance(init_args: _*).asInstanceOf[T]
}

Example usage:

scala> case class A(x:Double, y:Int)
defined class A
scala> newInstance[A](4.5.asInstanceOf[Object],3.asInstanceOf[Object])
res1: A = A(4.5,3)
like image 31
Martin Tapp Avatar answered Oct 09 '22 06:10

Martin Tapp