Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a TypeTag manually?

Tags:

scala

I'm interested in creating a TypeTag manually (since 2.10M5):

object X {
  import reflect.runtime.universe._
  def tt[A : TypeTag](a: A) = typeTag[A] // how to do this manually?
  val t = tt(List("")(_))
}

scalac -Xprint:typer <file>.scala results in

package <empty> {
  object X extends scala.AnyRef {
    def <init>(): X.type = {
      X.super.<init>();
      ()
    };
    import scala.reflect.runtime.`package`.universe._;
    def tt[A >: Nothing <: Any](a: A)(implicit evidence$1: reflect.runtime.universe.TypeTag[A]): reflect.runtime.universe.TypeTag[A] = scala.reflect.runtime.`package`.universe.typeTag[A](evidence$1);
    private[this] val t: reflect.runtime.universe.TypeTag[Int => String] = X.this.tt[Int => String](((x$1: Int) => immutable.this.List.apply[String]("").apply(x$1)))({
      val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe;
      val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader());
      $u.TypeTag.apply[Int => String]($m, {
        final class $typecreator1 extends TypeCreator {
          def <init>(): $typecreator1 = {
            $typecreator1.super.<init>();
            ()
          };
          def apply[U >: Nothing <: scala.reflect.base.Universe with Singleton]($m$untyped: scala.reflect.base.MirrorOf[U]): U#Type = {
            val $u: U = $m$untyped.universe;
            val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
            $u.TypeRef.apply($u.ThisType.apply($m.staticModule("scala").asModuleSymbol.moduleClass), $m.staticClass("scala.Function1"), scala.collection.immutable.List.apply[$u.Type]($m.staticClass("scala.Int").asTypeSymbol.asTypeConstructor, $m.staticClass("java.lang.String").asTypeSymbol.asTypeConstructor))
          }
        };
        new $typecreator1()
      })
    });
    <stable> <accessor> def t: reflect.runtime.universe.TypeTag[Int => String] = X.this.t
  }
}

It seems to be completely compiler magic because the types are hardcoded. Nevertheless is there a way to do this manually?

like image 266
kiritsuku Avatar asked Jul 15 '12 19:07

kiritsuku


3 Answers

Currently, there are three ways to create a TypeTag that supports generics manually. The first two depends on the scala-compiler, and with them you get type checking just like in compile time, as it is an actual code compilation:

import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox

val toolbox = currentMirror.mkToolBox()

def createTypeTag(tp: String): TypeTag[_] = {
  val ttree = toolbox.parse(s"scala.reflect.runtime.universe.typeTag[$tp]")
  toolbox.eval(ttree).asInstanceOf[TypeTag[_]]
}

If you need a serializable TypeTag and performance isn't your main concern, the first method is the most succinct. OTOH, if your use case needs to be very performant, beware that on-the-fly compilation might take a couple of seconds to finish.

The second method performs a little better:

def createTypeTag(tp: String): TypeTag[_] = {
  val ttagCall = s"scala.reflect.runtime.universe.typeTag[$tp]"
  val tpe = toolbox.typecheck(toolbox.parse(ttagCall), toolbox.TYPEmode).tpe.resultType.typeArgs.head

  TypeTag(currentMirror, new reflect.api.TypeCreator {
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
      assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  }
}

This second mode creates a typed tree and fetches the concrete types marked in the TypeTag, i.e., if your target is a List[String], it will be translated to scala.collection.immutable.List[String], since scala.List is only a type alias. That's actually the behavior expected from typeTag[List[String]].

The last method is to create a Type manually and use it to create the TypeTag. This method is error-prone, there is limited type checking, and it uses internal API. This is, however, the fastest way to manually obtain a TypeTag.

def createTypeTag(tp: String): TypeTag[_] = {
  val typs = // ... manipulate the string to extract type and parameters
  val typSym = currentMirror.staticClass(typs[0])
  val paramSym = currentMirror.staticClass(typs[1])

  val tpe = universe.internal.typeRef(NoPrefix, typSym, List(paramSym.selfType))

  val ttag = TypeTag(currentMirror, new TypeCreator {
    override def apply[U <: Universe with Singleton](m: Mirror[U]): U#Type = {
      assert(m == currentMirror, s"TypeTag[$tpe] defined in $currentMirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  })
}
like image 137
Carlos Melo Avatar answered Oct 09 '22 21:10

Carlos Melo


In M3 you could create a type tag in a very simple way, e.g.: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil)). It basically meant that once a type tag is created, it is forever bound to a certain classloader (namely the one that was used to load a symbol of scala.Int in the example above).

Back then it was fine, because we considered that we could have a one-size-fits-all mirror that accomodates all classloaders. That was convenient, because you could just write implicitly[TypeTag[T]] and the compiler would use that global mirror to instantiate a Type you requested.

Unfortunately later, based on feedback, we realized that this isn't going to work, and that we need multiple mirrors - each having its own classloader.

And then it became apparent that type tags need to be flexible, because once you write that implicitly[TypeTag[T]] the compiler doesn't have enough information what classloader you want to use. Basically there were two alternatives: 1) make type tags path-dependent on mirrors (so that one would be forced to explicitly provide a mirror every time a type tag is requested from the compiler), 2) transform type tags into type factories, capable of instantiating themselves in any mirror. Long story short, the first option didn't work, so we are where we are.

So currently type tags need to be created in quite a roundabout way, as outlined in https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143. You call a factory method defined in the companion TypeTag and provide two arguments: 1) a default mirror, where the type tag will be instantiated, 2) a type factory that can instantiate a type tag in arbitrary mirror. This is basically what the compiler did in the printout you've attached above.

Why I called this roundabout? Because to provide a type factory you need to manually subclass the scala.reflect.base.TypeFactory class. That's an unfortunate limitation of Scala type system on the border between function and dependently-typed methods.

like image 21
Eugene Burmako Avatar answered Oct 09 '22 21:10

Eugene Burmako


Based on Get TypeTag[A] from Class[A]:

import scala.reflect.runtime.universe._

def typeToTypeTag[T](
  tpe: Type,
  mirror: reflect.api.Mirror[reflect.runtime.universe.type]
): TypeTag[T] = {
  TypeTag(mirror, new reflect.api.TypeCreator {
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
      assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  })
}

For example this can be used to get the TypeTag for a part of another TypeTag:

def inside[A, B](tag: TypeTag[(A, B)]): (TypeTag[A], TypeTag[B]) = {
  val tpes = tag.tpe.asInstanceOf[TypeRefApi].args
  val tagA = typeToTypeTag[A](tpes(0), tag.mirror)
  val tagB = typeToTypeTag[B](tpes(1), tag.mirror)
  return (tagA, tagB)
}

This works in Scala 2.10.2:

scala> inside(typeTag[(Int, Double)])
res0: (reflect.runtime.universe.TypeTag[Int], reflect.runtime.universe.TypeTag[Double]) = (TypeTag[Int],TypeTag[Double])

The limitation of being tied to a particular Mirror is likely not a problem as long as you don't have multiple ClassLoaders.

like image 26
Daniel Darabos Avatar answered Oct 09 '22 20:10

Daniel Darabos