Scala宏创建字段和方法指针

时间:2014-12-06 20:48:05

标签: scala scala-macros

我想创建一个Scala宏&在一个字段的情况下,它将返回一个getter / setter对,而在一个方法的情况下,将返回一个部分应用的函数。如下所示:

trait ValRef[T] {
    def get(): T 
}

trait VarRef[T] extends ValRef[T] {
    def set(x: T): Unit
}

// Example:

case class Foo() {
   val v = "whatever"
   var u = 100
   def m(x: Int): Unit 
}

val x = new Foo
val cp: ValRef[String] = &x.v
val p: VarRef[Int] = &x.u
p.set(300)
val f: Int => Unit = &x.m
f(p.get())

我没有使用Scala宏的经验,但是我认为对于那些做的人来说它应该是相当简单的。

1 个答案:

答案 0 :(得分:3)

我最近开始阅读Scala的宏,并发现你的问题是一个有趣的练习。我只实现了指向valvar值的指针,但由于你只要求代码草图,我想我会分享到目前为止我发现的内容。

编辑:关于方法的指针:如果我没有误解某些内容,这已经是Scala的一个功能了。给定class Foo{ def m(i: Int): Unit = println(i)},您得到的函数为val f: Int => Unit = new Foo().m _

  • 如果您只对代码感兴趣,请滚动到答案的底部。

请注意,宏在编译时执行,因此必须进行预编译。如果您使用的是IntelliJ(或Eclipse),那么我建议将所有与宏相关的代码放在一个单独的项目中。

正如你所提到的,我们有两个指针特征

trait ValRef[T] {
  def get(): T
}

trait VarRef[T] extends ValRef[T] {
  def set(x: T): Unit
}

现在我们要实现一个方法&,对于给定的引用,即namequalifier.name,返回ValRef。如果引用引用可变值,则结果应为VarRef

def &[T](value: T): ValRef[T]

到目前为止,这只是常规的Scala代码。方法&接受任何表达式并返回与参数类型相同的ValRef

让我们定义一个实现&

逻辑的宏
def pointer[T: c.WeakTypeTag](c: scala.reflect.macros.blackbox.Context)(value: c.Expr[T]) = ???

签名应该主要是标准的:

  • c - Context - 包含使用宏的编译器收集的信息。
  • T是传递给&
  • 的表达式的类型
  • value对应&的参数,但由于该实现在Scala的AST上运行,因此它的类型为Expr[T]而不是原始类型T

有点特别的是使用WeakTypeTag,我并不完全了解自己。 documentation州:

  

实现中的类型参数可能带有WeakTypeTag上下文边界。在这种情况下,描述在应用程序站点实例化的实际类型参数的相应类型标记将在宏展开时传递。

有趣的部分是pointer的实施。由于只要调用方法&,编译器就会使用该方法的结果,它必须返回AST。因此,我们想要生成一棵树。问题是,树应该是什么样的?

幸运的是,由于Scala 2.11存在称为Quasiquotes的东西。 Quasiquotes可以帮助我们从字符串值构建树。

让我们首先简化问题:我们总是返回val,而不是区分varVarRef引用。对于由VarRef

生成的x.y
  • get()应该返回x.y
  • set(x)应执行x.y = x

因此,我们希望生成一个树,该树表示VarRef[T]的匿名子类的实例化。因为我们不能在Quasiquote中直接使用泛型类型T,所以我们首先需要类型的树表示,我们可以通过val tpe = value.tree.tpe获得

现在,我们的Quasiquote看起来如下:

q"""
  new VarRef[$tpe] {
    def get(): $tpe = $value

    def set(x: $tpe): Unit = {
      $value = x
    }
  }
"""

只要我们只创建指向var引用的指针,此实现就应该有效。但是,只要我们创建指向val引用的指针,编译就会失败,因为“重新分配给val”。因此,我们的宏需要区分这两者。

显然,Symbols提供了这种信息。我们希望仅为引用创建指针,这应该提供TermSymbol

val symbol: TermSymbol = value.tree.symbol.asTerm

现在,TermSymbol api为我们提供了方法isValisVar,但它们似乎只适用于局部变量。我不确定发现引用是var还是val的“正确方法”是什么,但以下似乎有效:

if(symbol.isVar || symbol.setter != NoSymbol) {

诀窍是,限定名称的符号似乎提供setter符号 iff ,它们是var引用。否则,setter会返回NoSymbol


因此宏代码如下所示:

trait ValRef[T] {
  def get(): T
}

trait VarRef[T] extends ValRef[T] {
  def set(x: T): Unit
}

object PointerMacro {

  import scala.language.experimental.macros

  def pointer[T: c.WeakTypeTag](c: scala.reflect.macros.blackbox.Context)(value: c.Expr[T]) = {
    import c.universe._

    val symbol: TermSymbol = value.tree.symbol.asTerm
    val tpe = value.tree.tpe

    if(symbol.isVar || symbol.setter != NoSymbol) {
      q"""
        new VarRef[$tpe] {
          def get(): $tpe = $value

          def set(x: $tpe): Unit = {
            $value = x
          }
        }
      """
    } else {
      q"""
        new ValRef[$tpe] {
          def get(): $tpe = $value
        }
      """
    }
  }

  def &[T](value: T): ValRef[T] = macro pointer[T]
}

如果编译此代码并将其添加到项目的类路径中,那么您应该能够创建如下指针:

case class Foo() {
  val v = "whatever"
  var u = 100
}

object Example{
  import PointerMacro.&

  def main(args: Array[String]): Unit = {
    val x = new Foo
    val mainInt = 90
    var mainString = "this is main"

    val localValPointer: ValRef[Int] = &(mainInt)
    val localVarPointer: VarRef[String] = &(mainString).asInstanceOf[VarRef[String]]
    val memberValPointer: ValRef[String] = &(x.v)
    val memberVarPointer: VarRef[Int] = &(x.u).asInstanceOf[VarRef[Int]]

    println(localValPointer.get())
    println(localVarPointer.get())
    println(memberValPointer.get())
    println(memberVarPointer.get())

    localVarPointer.set("Hello World")
    println(localVarPointer.get())

    memberVarPointer.set(62)
    println(memberVarPointer.get())

  }
}

,运行时应打印

90
this is main
whatever
100
Hello World
62