Подтвердить что ты не робот

Как создать TypeTag вручную?

Я заинтересован в создании TypeTag вручную (начиная с 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 приводит к

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
  }
}

Кажется, что это полностью компилятор, потому что типы жестко закодированы. Тем не менее, есть ли способ сделать это вручную?

4b9b3361

Ответ 1

В M3 вы можете создать тег типа очень простым способом, например: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil)). В основном это означало, что после создания тега типа он всегда привязан к определенному загрузчику классов (а именно к тому, который использовался для загрузки символа scala.Int в примере выше).

В то время все было хорошо, потому что мы считали, что у нас может быть зеркало одного размера, подходящее для всех загрузчиков классов. Это было удобно, потому что вы могли просто написать implicitly[TypeTag[T]], и компилятор будет использовать это глобальное зеркало для создания экземпляра запрашиваемого вами типа.

К сожалению, позже, основываясь на отзывах, мы поняли, что это не сработает, и что нам нужно несколько зеркал - каждый из которых имеет свой собственный загрузчик классов.

И тогда стало очевидно, что теги типа должны быть гибкими, потому что, как только вы пишете, что implicitly[TypeTag[T]], у компилятора недостаточно информации о том, какой загрузчик классов вы хотите использовать. В основном было две альтернативы: 1) сделать теги типа зависимыми от зеркал (так, чтобы каждый из них был вынужден явно предоставлять зеркало каждый раз, когда тег типа запрашивается у компилятора), 2) преобразовывать теги типа в фабрики типов, способные экземпляров себя в любом зеркале. Короче говоря, первый вариант не сработал, поэтому мы находимся там, где мы находимся.

Таким образом, теги текущего типа должны быть созданы довольно круто, как описано в https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143. Вы вызываете метод factory, определенный в компаньоне TypeTag, и предоставляете два аргумента: 1) зеркало по умолчанию, где будет создан экземпляр типа, 2) тип factory, который может создавать экземпляр типа в произвольном зеркале. Это в основном то, что сделал компилятор в распечатке, которую вы указали выше.

Почему я назвал эту кольцевую развязку? Поскольку для предоставления типа factory вам необходимо вручную подклассифицировать класс scala.reflect.base.TypeFactory. Это несчастливое ограничение системы типа Scala на границе между функциями и зависимыми методами.

Ответ 2

На основе Получить TypeTag [A] из класса [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]
    }
  })
}

Например, это можно использовать для получения TypeTag для части другого 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)
}

Это работает в Scala 2.10.2:

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

Ограничение привязки к определенному Mirror, вероятно, не является проблемой, если у вас нет нескольких ClassLoader s.

Ответ 3

Определение функции

def tt[A : TypeTag](a: A) = typeTag[A]

- это еще один способ записи

def tt(a: A)(implicit tag: TypeTag[A]) = tag

что означает, что экземпляр тега создается неявным образом компилятором. Причиной этого является то, что компилятор Scala решает проблему стирания типа JVM путем жесткого кодирования информации об удаленных типах.

Если вы не знакомы с проблемой стирания типа, JVM не хранит информацию о параметрах типа, например, тип Seq[Set[Int]] будет восприниматься JVM просто как Seq[_], и будет вам не удастся узнать информацию о пропавшем типе с отражением.

Ответ 4

В настоящее время существует три способа создания TypeTag , который поддерживает дженерики вручную. Первые два зависят от scala-compiler, и с ними вы получаете проверку типов, как и во время компиляции, поскольку это фактическая компиляция кода:

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[_]]
}

Если вам нужен сериализуемый TypeTag, и производительность не является вашей главной задачей, первый метод является самым кратким. OTOH, если ваш случай использования должен быть очень эффективным, остерегайтесь, что компиляция на лету может занять несколько секунд.

Второй способ выполняет немного лучше:

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]
    }
  }
}

Этот второй режим создает типизированное дерево и выбирает конкретные типы, помеченные в TypeTag, т.е. если ваша цель является List[String], она будет переведена на scala.collection.immutable.List[String], так как scala.List является только типом псевдоним. Это на самом деле поведение, ожидаемое от typeTag[List[String]].

Последний способ - создать Type вручную и использовать его для создания TypeTag. Этот метод подвержен ошибкам, существует ограниченная проверка типов, и он использует внутренний API. Это, однако, самый быстрый способ вручную получить 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]
    }
  })
}