2014-12-06 2 views
1

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

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 макросов, однако я предполагаю, что это должно быть довольно просто для тех, кто это делает.

+2

Это заявление, а не вопрос, вопрос, возможно ли это или нет? – johanandren

+0

Будет оценен эскиз кода или указатель на подобное. Спасибо. –

ответ

3

Недавно я начал читать о макросах 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, которое я не совсем понимаю.В documentation указывается:

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

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

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

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

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

Итак, мы хотим, чтобы создать дерево, которое представляет экземпляр анонимного подкласса VarRef[T]. Потому что мы не можем использовать общий тип T непосредственно в Quasiquote, сначала нужно дерево представление типа, который мы можем получить от 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 апи предоставляет нам методы isVal и isVar, но они, кажется, работают только для локальных переменных. Я не уверен, что «правильный путь», чтобы выяснить, действительно ли ссылка является var или val есть, но следующий, кажется, работает:

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^