Недавно я начал читать о макросах 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
Это заявление, а не вопрос, вопрос, возможно ли это или нет? – johanandren
Будет оценен эскиз кода или указатель на подобное. Спасибо. –