从宏获取带有匿名类方法的结构类型


181

假设我们要编写一个宏,该宏使用一些类型成员或方法定义一个匿名类,然后使用这些方法创建该类的实例,该实例被静态类型化为结构类型,依此类推。在2.10中的宏系统中是可行的。 0,并且类型成员部分非常简单:

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

(这是提供我方法的ReflectionUtils一种便利特性constructor。)

这个宏使我们可以将匿名类的类型成员的名称指定为字符串文字:

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6

请注意,它是正确键入的。我们可以确认一切都按预期进行:

scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>

现在假设我们尝试用一种方法做同样的事情:

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

但是,当我们尝试时,我们没有得到结构类型:

scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492

但是,如果我们在那里放一个额外的匿名类:

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

有用:

scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

这是非常方便,它可以让你做这样的事情,例如-但我不明白为什么它的工作原理,以及该类型成员版本的作品,但不是bar。我知道这可能不是定义的行为,但这有意义吗?有没有一种更干净的方法来从宏获取结构类型(带有方法)?


14
有趣的是,如果您在REPL中编写相同的代码而不是在宏中生成代码,则它可以工作:scala> {final class anon {def x = 2}; new anon} res1:AnyRef {def x:Int} = anon $ 1 @ 5295c398。感谢您的举报!这周我来看一下。
Eugene Burmako 2013年

1
请注意,我在这里提出了一个问题。
Travis Brown

不,不是阻止者,谢谢-每当我需要时,额外的匿名类技巧就对我有用。我只是注意到这个问题上有几个反对意见,并对这个状态感到好奇。
特拉维斯·布朗

3
类型成员部分非常容易-> wTF?当然,您非常的裂缝!)
ZaoTaoBao13年

3
这里共有153个投票,而scala-lang.org上只有1个投票。那里更多的投票可能会更快地解决它?
moodboom

Answers:


9

Travis 在此重复回答了这个问题。在跟踪器和Eugene的讨论中都有指向该问题的链接(在评论和邮件列表中)。

在类型检查器中著名的“ Skylla and Charybdis”部分中,我们的英雄决定了什么应摆脱黑暗的匿名性,并将光视为结构类型的成员。

有两种方法可以欺骗类型检查器(这并不意味着Odysseus会抱抱绵羊)。最简单的方法是插入一个伪语句,以使该块看起来不像是一个匿名类,后​​面是其实例化。

如果打字员注意到您是外界未引用的公共术语,它将使您变得私密。

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

2
我只是注意到,我实际上是在此问题本身中提供了第一个解决方法(此处未作准引用)。我很高兴得到这个答案来解决问题-我想我一直在模糊地等待错误修复。
特拉维斯·布朗

@TravisBrown我敢打赌,您在Bat Belt中也有其他工具。提醒一下:我假设您的AST是“旧的大括号花样”,但现在我看到ClassDef / Apply并没有包装在自己的Block中new $anon {}。我的另一个anon收获是,将来我不会在具有准引用或类似特殊名称的宏中使用。
som-snytt

q“ $ {s:String}”语法会延迟一点,尤其是在使用天堂的情况下。因此更像是下个月而不是下周。
Denys Shabalin

@ som-snytt @ denys-shabalin,对于结构类型a-la是否有一种特殊的欺骗手段shapeless.Generic?尽管我最想强制使用Aux模式返回类型,但编译器拒绝查看结构类型。
flavian
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.