Макрос Scala для создания указателей полей и методов

Я хотел бы создать макрос Scala &, который в случае поля будет возвращать пару получатель / установщик, а в случае метода - частично примененную функцию. Что-то вроде следующего:

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 ответ

Недавно я начал читать о макросах Scala и нашел ваш вопрос интересным упражнением. Я только реализовал указатели на val а также var значения, но так как вы запрашиваете только набросок кода, я подумал, что поделюсь тем, что я нашел до сих пор.

Изменить: Относительно указателей на методы: Если я не понимаю что-то неправильно, это уже особенность 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
}

Теперь мы хотим реализовать метод & что для данной ссылки, т.е. name или же qualifier.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 соответствует аргументу &, но так как реализация работает в AST Scala, она имеет тип Expr[T] а не оригинального типа T

Немного особенным является использование WeakTypeTagЯ не совсем понимаю себя. В документации говорится:

Параметры типа в реализации могут поставляться с границами контекста WeakTypeTag. В этом случае соответствующие теги типа, описывающие фактические аргументы типа, созданные на сайте приложения, будут переданы при расширении макроса.

Интересной частью является реализация pointer, Так как результат метода должен использоваться компилятором всякий раз, когда метод & называется, он должен вернуть AST. Следовательно, мы хотим сгенерировать дерево. Вопрос в том, как должно выглядеть дерево?

К счастью, начиная с Scala 2.11 существует нечто, называемое квазицитаты. Квазицитаты могут помочь нам в построении дерева из строкового значения.

Давайте сначала упростим задачу: вместо того, чтобы различать val а также var ссылки, мы всегда возвращаем VarRef, Для VarRef создано x.y

  • get() должен вернуться x.y
  • set(x) должен выполнить x.y = x

Итак, мы хотим создать дерево, которое представляет собой экземпляр анонимного подкласса VarRef[T], Потому что мы не можем использовать универсальный тип T непосредственно в квазицитате нам сначала нужно представление дерева типа, которое мы можем получить val tpe = value.tree.tpe

Теперь наша квазиквота выглядит следующим образом:

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

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

Эта реализация должна работать, пока мы только создаем указатели на var Рекомендации. Однако, как только мы создадим указатель на val ссылка, компиляция не удалась из-за "переназначения в val". Следовательно, наш макрос должен различать два.

Очевидно, символы предоставляют такую ​​информацию. Мы ожидаем, что указатели будут создаваться только для ссылок, которые должны обеспечить TermSymbol,

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

Теперь TermSymbol API предоставляет нам методы isVal а также isVar, но они, кажется, работают только для локальных переменных. Я не уверен, что "правильный путь", чтобы узнать, является ли ссылка var или же val есть, но, кажется, работает следующее:

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

Хитрость в том, что символы квалифицированных имен, кажется, обеспечивают setter символ, если они 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]
}

Если вы скомпилируете этот код и добавите его в classpath вашего проекта, то вы сможете создавать указатели, подобные этому:

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
Другие вопросы по тегам