scala - Obtener un tipo estructural con los métodos de una clase anónima de una macro
macros scala-2.10 (1)
Supongamos que queremos escribir una macro que define una clase anónima con algunos miembros o métodos de tipo, y luego creamos una instancia de esa clase que está tipada estáticamente como un tipo estructural con esos métodos, etc. Esto es posible con el macro sistema en 2.10. 0, y la parte de miembro de tipo es extremadamente fácil:
object MacroExample extends ReflectionUtils {
import scala.language.experimental.macros
import scala.reflect.macros.Context
def foo(name: String): Any = macro foo_impl
def foo_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}
}
(Donde ReflectionUtils
es un rasgo de conveniencia que proporciona mi método constructor
).
Esta macro nos permite especificar el nombre del miembro de tipo de la clase anónima como una cadena literal:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Tenga en cuenta que está apropiadamente tipeado. Podemos confirmar que todo está funcionando como se esperaba:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Ahora supongamos que tratamos de hacer lo mismo con un método:
def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
DefDef(
Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}
Pero cuando lo probamos, no obtenemos un tipo estructural:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Pero si incluimos una clase anónima adicional:
def baz(name: String): Any = macro baz_impl
def baz_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
val wrapper = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
DefDef(
Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
ClassDef(
Modifiers(Flag.FINAL), wrapper, Nil,
Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
),
Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
))
}
Funciona:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Esto es extremadamente útil, te permite hacer cosas como this , por ejemplo, pero no entiendo por qué funciona, y la versión del miembro de tipo funciona, pero no la bar
. Sé que esto puede no ser un comportamiento definido , pero ¿tiene algún sentido? ¿Hay alguna manera más clara de obtener un tipo estructural (con los métodos) de una macro?
Esta pregunta es respondida por Travis here por duplicado. Hay enlaces al problema en el rastreador y a la discusión de Eugene (en la lista de comentarios y correo).
En la famosa sección "Skylla y Charybdis" del verificador de tipos, nuestro héroe decide qué se escapará del oscuro anonimato y verá la luz como un miembro del tipo estructural.
Hay un par de formas de engañar al verificador de tipos (que no implica la estratagema de Odysseus de abrazar a una oveja). Lo más simple es insertar una declaración ficticia para que el bloque no se vea como una clase anónima seguida de su creación de instancias.
Si Typer nota que eres un término público que no está referenciado por el exterior, te hará privado.
object Mac {
import scala.language.experimental.macros
import scala.reflect.macros.Context
/* Make an instance of a structural type with the named member. */
def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val anon = TypeName(c.freshName)
// next week, val q"${s: String}" = name.tree
val Literal(Constant(s: String)) = name.tree
val A = TermName(s)
val dmmy = TermName(c.freshName)
val tree = q"""
class $anon {
def $A(i: Int): Int = 2 * i
}
val $dmmy = 0
new $anon
"""
// other ploys
//(new $anon).asInstanceOf[{ def $A(i: Int): Int }]
// reference the member
//val res = new $anon
//val $dmmy = res.$A _
//res
// the canonical ploy
//new $anon { } // braces required
c.Expr(tree)
}
}