mgo*_*nto 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
}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,我已经成功了c.universe.Type fieldMemberType.这表示对象中某个字段的类型.一旦我得到了,我想TypeBuilder在reify中创建一个新对象.TypeBuilder是一个带抽象参数的抽象类.这个抽象参数是fieldType.我希望这fieldType是我之前找到的类型.
运行此处显示的代码会返回一个fieldMemberType not found.有什么方法可以让我fieldMemberType在reify子句中工作吗?
Tra*_*own 24
问题是你传递给的代码reify本质上是逐字放置在扩展宏的位置,fieldMemberType并不代表那里的任何东西.
在某些情况下,您可以使用splice将在宏扩展时具有的表达式隐藏到您正在实现的代码中.例如,如果我们尝试创建此特征的实例:
trait Foo { def i: Int }
Run Code Online (Sandbox Code Playgroud)
并且在宏扩展时有这个变量:
val myInt = 10
Run Code Online (Sandbox Code Playgroud)
我们可以写下面的内容:
reify { new Foo { def i = c.literal(myInt).splice } }
Run Code Online (Sandbox Code Playgroud)
这不会在这里起作用,这意味着你将不得不忘记好一点,reify并手工写出AST.不幸的是,你会发现这种情况发生了很多.我的标准方法是启动一个新的REPL并输入如下内容:
import scala.reflect.runtime.universe._
trait TypeBuilder { type fieldType }
showRaw(reify(new TypeBuilder { type fieldType = String }))
Run Code Online (Sandbox Code Playgroud)
这将吐出几行AST,然后您可以剪切并粘贴到宏定义中作为起点.然后你摆弄它,取代这样的事情:
Ident(TypeBuilder)
Run Code Online (Sandbox Code Playgroud)
有了这个:
Ident(newTypeName("TypeBuilder"))
Run Code Online (Sandbox Code Playgroud)
并FINAL用Flag.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)
)
)
Run Code Online (Sandbox Code Playgroud)
哪里anon是你预先创建了您的匿名类的类型名称,constructor是一种方便的方法我用做这种事情少一点丑陋(你可以在下面找到其定义这个完整的工作示例).
现在,如果我们像这样包起来表达这个,我们可以写出如下:
scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = $1$$1@fb3f1f3
Run Code Online (Sandbox Code Playgroud)
所以它有效.我们采用了一个c.universe.Type(我从WeakTypeTag类型参数中获取builderWithType,但它将以与任何旧的完全相同的方式工作Type)并使用它来定义我们的TypeBuilder特征的类型成员.
对于您的用例,有一种比树编写更简单的方法.事实上,我一直用它来遮挡树木,因为用树木编程真的很困难.我更喜欢计算类型并使用reify来生成树.这使得宏更健壮,更"卫生",减少了编译时错误.使用树的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))
Run Code Online (Sandbox Code Playgroud)