13

我一直在使用 Scala 宏,并在宏中有以下代码:

    val fieldMemberType = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be another thing")
    }

    reify{
      new TypeBuilder() {
        type fieldType = fieldMemberType.type
      }
    }

如您所见,我设法获得了一个c.universe.Type fieldMemberType. 这表示对象中某个字段的类型。一旦我明白了,我想TypeBuilder在 reify 中创建一个新对象。TypeBuilder是一个带有抽象参数的抽象类。这个抽象参数是fieldType。我希望这fieldType是我以前发现的类型。

运行此处显示的代码会返回一个fieldMemberType not found. 有什么方法可以让我fieldMemberType在 reify 子句中工作?

4

2 回答 2

25

问题是您传递给的代码reify基本上将逐字放置在宏被扩展的位置,并且在fieldMemberType那里没有任何意义。

在某些情况下,您可以使用splice将宏扩展时的表达式偷偷带入您正在具体化的代码中。例如,如果我们试图创建这个 trait 的一个实例:

trait Foo { def i: Int }

并且在宏扩展时有这个变量:

val myInt = 10

我们可以这样写:

reify { new Foo { def i = c.literal(myInt).splice } }

这在这里行不通,这意味着您将不得不忘记漂亮的小事reify并手动写出 AST。不幸的是,你会发现这种情况经常发生。我的标准方法是启动一个新的 REPL 并输入如下内容:

import scala.reflect.runtime.universe._

trait TypeBuilder { type fieldType }

showRaw(reify(new TypeBuilder { type fieldType = String }))

这将吐出几行 AST,然后您可以将其剪切并粘贴到宏定义中作为起点。然后你摆弄它,替换这样的东西:

Ident(TypeBuilder)

有了这个:

Ident(newTypeName("TypeBuilder"))

FINALFlag.FINAL等等。我希望toStringAST 类型的方法更准确地对应于构建它们所需的代码,但是您很快就会了解需要更改的内容。你最终会得到这样的东西:

c.Expr(
  Block(
    ClassDef(
      Modifiers(Flag.FINAL),
      anon,
      Nil,
      Template(
        Ident(newTypeName("TypeBuilder")) :: Nil,
        emptyValDef,
        List(
          constructor(c),
          TypeDef(
            Modifiers(),
            newTypeName("fieldType"),
            Nil,
            TypeTree(fieldMemberType)
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  )
)

anon您预先为匿名类创建的类型名称在哪里,并且constructor是我用来使这种事情不那么可怕的一种方便方法(您可以在这个完整的工作示例的末尾找到它的定义)。

现在,如果我们将这个表达式包装成这样的东西,我们可以编写以下内容:

scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = $1$$1@fb3f1f3

所以它有效。我们采用了一个c.universe.Type(我从WeakTypeTagon 的类型参数中得到builderWithType它,但它与任何 old 的工作方式完全相同)并用它来定义我们特征Type的类型成员。TypeBuilder

于 2012-12-10T11:09:34.153 回答
7

对于您的用例,有一种比树编写更简单的方法。事实上,我一直使用它来阻止树木,因为用树木进行编程真的很困难。我更喜欢计算类型并使用 reify 来生成树。这使得宏更加健壮和“卫生”并且编译时错误更少。IMO 使用树必须是最后的手段,仅适用于少数情况,例如树转换或元组等类型家族的泛型编程。

这里的技巧是定义一个函数作为类型参数,你想在 reify 主体中使用的类型,在 WeakTypeTag 上绑定一个上下文。然后,您可以通过显式传递 WeakTypeTags 来调用此函数,这要归功于上下文 WeakTypeTag 方法。

因此,在您的情况下,这将给出以下结果。

  val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be            another thing")
  }

  def genRes[T: WeakTypeTag] = reify{
    new TypeBuilder() {
      type fieldType = T
    }
  }

  genRes(c.WeakTypeTag(fieldMemberType))
于 2012-12-10T23:03:48.187 回答