Макрос 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