scala macros scala-2.10 structural-typing scala-macros

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