如何在reify子句中使用Scala宏中计算的类型?

时间:2012-12-10 05:27:12

标签: scala scala-2.10 scala-macros

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

    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。这表示对象中某个字段的类型。一旦我得到了,我想在reify中创建一个新的TypeBuilder对象。 TypeBuilder是一个带抽象参数的抽象类。此抽象参数为fieldType。我希望这个fieldType成为我之前找到的类型。

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

2 个答案:

答案 0 :(得分:24)

问题在于,传递给reify的代码实际上是逐字放置在扩展宏的位置,而fieldMemberType并不代表任何东西。< / p>

在某些情况下,您可以使用splice将您在宏扩展时拥有的表达式隐藏到您正在实现的代码中。例如,如果我们尝试创建此特征的实例:

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,依此类推。我希望AST类型的toString方法更准确地与构建它们所需的代码相对应,但是您很快就会了解需要更改的内容。你最终会得到这样的东西:

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是一种方便的方法,我用来使这种事情变得不那么可怕(你可以找到)它在this complete working example)末尾的定义。

现在,如果我们将此表达式包含在this之类的内容中,我们可以编写以下内容:

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

所以它有效。我们采用了c.universe.Type(我从WeakTypeTag上的类型参数的builderWithType处获得了Type,但它与旧的TypeBuilder完全相同)并用它来定义{{1}}特征的类型成员。

答案 1 :(得分:6)

对于您的用例,有一种比树编写更简单的方法。事实上,我一直用它来遮挡树木,因为用树木编程真的很困难。我更喜欢计算类型并使用reify来生成树。这使得更加健壮和卫生&#34;宏和较少的编译时错误。使用树的IMO必须是最后的手段,仅适用于少数情况,例如树变换或类型系列的通用编程,如元组。

这里的技巧是定义一个函数作为类型参数,你想在reify体中使用的类型,以及WeakTypeTag上的上下文绑定。然后通过显式传递可以从Universe类型构建的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))