Scala: O que é um TypeTag e como eu o uso?


361

Tudo o que sei sobre as TypeTags é que elas substituíram os Manifestos. As informações na Internet são escassas e não me proporcionam uma boa noção do assunto.

Então, eu ficaria feliz se alguém compartilhasse um link para alguns materiais úteis sobre TypeTags, incluindo exemplos e casos de uso populares. Respostas e explicações detalhadas também são bem-vindas.


11
O seguinte artigo da documentação do Scala descreve tanto o quê quanto o porquê das tags de tipo, bem como como usá-las em seu código: docs.scala-lang.org/overviews/reflection/…
btiernay

Respostas:


563

A TypeTagresolve o problema de que os tipos do Scala são apagados no tempo de execução (apagamento do tipo). Se nós queremos fazer

class Foo
class Bar extends Foo

def meth[A](xs: List[A]) = xs match {
  case _: List[String] => "list of strings"
  case _: List[Foo] => "list of foos"
}

receberemos avisos:

<console>:23: warning: non-variable type argument String in type pattern List[String]↩
is unchecked since it is eliminated by erasure
         case _: List[String] => "list of strings"
                 ^
<console>:24: warning: non-variable type argument Foo in type pattern List[Foo]↩
is unchecked since it is eliminated by erasure
         case _: List[Foo] => "list of foos"
                 ^

Para resolver este problema, os Manifestos foram apresentados ao Scala. Mas eles têm o problema de não poder representar muitos tipos úteis, como tipos dependentes de caminho:

scala> class Foo{class Bar}
defined class Foo

scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
warning: there were 2 deprecation warnings; re-run with -deprecation for details
m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]

scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@681e731c
b1: f1.Bar = Foo$Bar@271768ab

scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@3e50039c
b2: f2.Bar = Foo$Bar@771d16b9

scala> val ev1 = m(f1)(b1)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar

scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar

scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true

Assim, eles são substituídos por TypeTags , que são muito mais simples de usar e bem integrados à nova API de reflexão. Com eles, podemos resolver o problema acima sobre tipos dependentes de caminho de maneira elegante:

scala> def m(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev
m: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])↩
reflect.runtime.universe.TypeTag[f.Bar]

scala> val ev1 = m(f1)(b1)
ev1: reflect.runtime.universe.TypeTag[f1.Bar] = TypeTag[f1.Bar]

scala> val ev2 = m(f2)(b2)
ev2: reflect.runtime.universe.TypeTag[f2.Bar] = TypeTag[f2.Bar]

scala> ev1 == ev2 // the result is correct, the type tags are different
res30: Boolean = false

scala> ev1.tpe =:= ev2.tpe // this result is correct, too
res31: Boolean = false

Eles também são fáceis de usar para verificar os parâmetros de tipo:

import scala.reflect.runtime.universe._

def meth[A : TypeTag](xs: List[A]) = typeOf[A] match {
  case t if t =:= typeOf[String] => "list of strings"
  case t if t <:< typeOf[Foo] => "list of foos"
}

scala> meth(List("string"))
res67: String = list of strings

scala> meth(List(new Bar))
res68: String = list of foos

Neste ponto, é extremamente importante entender o uso =:=(tipo igualdade) e <:<(relação de subtipo) para verificações de igualdade. Nunca use ==ou !=, a menos que você saiba absolutamente o que faz:

scala> typeOf[List[java.lang.String]] =:= typeOf[List[Predef.String]]
res71: Boolean = true

scala> typeOf[List[java.lang.String]] == typeOf[List[Predef.String]]
res72: Boolean = false

O último verifica a igualdade estrutural, o que geralmente não é o que deve ser feito, porque não se importa com itens como prefixos (como no exemplo).

A TypeTagé completamente gerado pelo compilador, o que significa que o compilador cria e preenche um TypeTagquando se chama um método que espera tal TypeTag. Existem três formas diferentes de tags:

ClassTagsubstitui ClassManifestconsiderando que TypeTagé mais ou menos o substituto de Manifest.

O primeiro permite trabalhar totalmente com matrizes genéricas:

scala> import scala.reflect._
import scala.reflect._

scala> def createArr[A](seq: A*) = Array[A](seq: _*)
<console>:22: error: No ClassTag available for A
       def createArr[A](seq: A*) = Array[A](seq: _*)
                                           ^

scala> def createArr[A : ClassTag](seq: A*) = Array[A](seq: _*)
createArr: [A](seq: A*)(implicit evidence$1: scala.reflect.ClassTag[A])Array[A]

scala> createArr(1,2,3)
res78: Array[Int] = Array(1, 2, 3)

scala> createArr("a","b","c")
res79: Array[String] = Array(a, b, c)

ClassTag fornece apenas as informações necessárias para criar tipos em tempo de execução (que são apagados):

scala> classTag[Int]
res99: scala.reflect.ClassTag[Int] = ClassTag[int]

scala> classTag[Int].runtimeClass
res100: Class[_] = int

scala> classTag[Int].newArray(3)
res101: Array[Int] = Array(0, 0, 0)

scala> classTag[List[Int]]
res104: scala.reflect.ClassTag[List[Int]] =ClassTag[class scala.collection.immutable.List]

Como se pode ver acima, eles não se importam com o apagamento de tipo; portanto, se quisermos TypeTagusar tipos "completos" :

scala> typeTag[List[Int]]
res105: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

scala> typeTag[List[Int]].tpe
res107: reflect.runtime.universe.Type = scala.List[Int]

scala> typeOf[List[Int]]
res108: reflect.runtime.universe.Type = scala.List[Int]

scala> res107 =:= res108
res109: Boolean = true

Como se pode ver, o método tpedos TypeTagresultados é completo Type, o mesmo que obtemos quando typeOfé chamado. Obviamente, é possível usar ambos, ClassTage TypeTag:

scala> def m[A : ClassTag : TypeTag] = (classTag[A], typeTag[A])
m: [A](implicit evidence$1: scala.reflect.ClassTag[A],implicit evidence$2: reflect.runtime.universe.TypeTag[A])(scala.reflect.ClassTag[A], reflect.runtime.universe.TypeTag[A])

scala> m[List[Int]]
res36: (scala.reflect.ClassTag[List[Int]],↩
        reflect.runtime.universe.TypeTag[List[Int]]) =(scala.collection.immutable.List,TypeTag[scala.List[Int]])

A questão remanescente agora é qual é o sentido WeakTypeTag? Em resumo, TypeTagrepresenta um tipo concreto (isso significa que permite apenas tipos totalmente instanciados) enquanto WeakTypeTagapenas permite qualquer tipo. Na maioria das vezes, não se importa com o que é (o que significa que TypeTagdeve ser usado), mas, por exemplo, quando são usadas macros que devem funcionar com tipos genéricos, são necessárias:

object Macro {
  import language.experimental.macros
  import scala.reflect.macros.Context

  def anymacro[A](expr: A): String = macro __anymacro[A]

  def __anymacro[A : c.WeakTypeTag](c: Context)(expr: c.Expr[A]): c.Expr[A] = {
    // to get a Type for A the c.WeakTypeTag context bound must be added
    val aType = implicitly[c.WeakTypeTag[A]].tpe
    ???
  }
}

Se alguém substituir WeakTypeTagpor TypeTagum erro é lançado:

<console>:17: error: macro implementation has wrong shape:
 required: (c: scala.reflect.macros.Context)(expr: c.Expr[A]): c.Expr[String]
 found   : (c: scala.reflect.macros.Context)(expr: c.Expr[A])(implicit evidence$1: c.TypeTag[A]): c.Expr[A]
macro implementations cannot have implicit parameters other than WeakTypeTag evidences
             def anymacro[A](expr: A): String = macro __anymacro[A]
                                                      ^

Para uma explicação mais detalhada sobre as diferenças entre TypeTage WeakTypeTagconsulte esta pergunta: Macros Scala: "não é possível criar TypeTag a partir de um tipo T com parâmetros de tipo não resolvidos"

O site oficial da documentação de Scala também contém um guia para reflexão .


19
Obrigado pela sua resposta! Alguns comentários: 1) ==para tipos representa igualdade estrutural, não referência a igualdade. =:=levar em consideração equivalências de tipo (mesmo que não óbvias, como equivalências de prefixos que vêm de espelhos diferentes), 2) Ambos TypeTage AbsTypeTagsão baseados em espelhos. A diferença é que TypeTagapenas tipos totalmente permite instanciados (isto é, sem quaisquer parâmetros de tipo ou referências sumário membros de tipo), 3) uma explicação pormenorizada aqui: stackoverflow.com/questions/12093752
Eugene Burmako

10
4) Os manifestos têm o problema de não serem capazes de representar muitos tipos úteis. Essencialmente, eles podem expressar apenas refs de tipo (tipos simples como Inte tipos genéricos como List[Int]), excluindo tipos Scala como, por exemplo, refinamentos, tipos dependentes de caminho, existenciais, tipos anotados. Também se manifesta são um parafuso, por isso eles não podem usar o vasto conhecimento que posesses compilador para, digamos, calcular a linearização de um tipo, descobrir se um tipo subtipos outro, etc.
Eugene Burmako

9
5) Para os tipos de contraste, as tags não são "melhor integradas", elas são simplesmente integradas à nova API de reflexão (ao contrário dos manifestos que não estão integrados a nada). Isso proporciona tipo marcas acesso a determinados aspectos do compilador, por exemplo, para Types.scala(7kloc de código que sabe como tipos são suportados para trabalhar em conjunto), Symbols.scala(3kloc de código que sabe como símbolo mesas de trabalho), etc.
Eugene Burmako

9
6) ClassTagé um substituto exato para o drop-in ClassManifest, enquanto TypeTagé mais ou menos um substituto para Manifest. Mais ou menos, porque: 1) as tags de tipo não carregam rasuras, 2) os manifestos são um grande hack e desistimos de emular seu comportamento com as tags de tipo. O número 1 pode ser corrigido usando os limites de contexto ClassTag e TypeTag quando você precisa de apagamentos e tipos, e geralmente não se importa com o número 2, porque torna-se possível jogar fora todos os hacks e usar a API de reflexão completa em vez de.
Eugene Burmako 02/09/12

11
Eu realmente espero que o compilador Scala se livre dos recursos descontinuados em algum momento, para tornar o conjunto de recursos disponíveis mais ortogonal. É por isso que gosto do novo suporte a macros porque ele oferece o potencial de limpar o idioma, separando alguns dos recursos em bibliotecas independentes que não fazem parte do idioma base.
Alexandru Nedelcu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.