Использование макросов в Scala для создания методов
Я хочу генерировать псевдонимы методов, используя макросы аннотаций в Scala 2.11+. Я даже не уверен, что это вообще возможно. Если да, то как?
пример - учитывая это, ниже, я хочу, чтобы макросы аннотация к расширению в
class Socket {
@alias(aliases = Seq("!", "ask", "read"))
def load(n: Int): Seq[Byte] = {/* impl */}
}
Я хочу, чтобы выше генерировать заглушки метода синонимов следующим образом:
class Socket {
def load(n: Int): Seq[Byte] = // ....
def !(n: Int) = load(n)
def ask(n: Int) = load(n)
def read(n: Int) = load(n)
}
выше, конечно, шутливый пример, но я вижу, что этот метод полезен для автоматической генерации версий синхронизации / асинхронности API или в DSLs с большим количеством синонимов. Можно ли также выставить эти сгенерированные методы в Scaladoc? Возможно ли это с помощью Scala meta?
Примечание: то, что я прошу, сильно отличается от:https://github.com/ktoso/scala-macro-method-alias
также, пожалуйста, не отметить это как дубликат этой поскольку вопрос немного отличается и многое изменилось в Scala macro land за последние 3 года.
1 ответов
это кажется невозможным точно так, как указано. Использование аннотации макроса для члена класса не позволяет управлять деревом самого класса. То есть, когда вы аннотируете метод в классе с аннотацией макроса,macroTransform(annottees: Any*)
будет вызван, но единственным аннотантом будет сам метод.
я смог получить доказательство концепции, работающее с двумя аннотациями. Это, очевидно, не так приятно, как просто аннотировать класс, но я не могу придумать другого способа он.
вам понадобится:
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
идея заключается в том, что вы можете аннотировать каждый метод с этой аннотацией, чтобы аннотация макроса в родительском классе могла найти, какие методы вы хотите развернуть.
class alias(aliases: String *) extends StaticAnnotation
потом макрос:
// Annotate the containing class to expand aliased methods within
@compileTimeOnly("You must enable the macro paradise plugin.")
class aliased extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AliasMacroImpl.impl
}
object AliasMacroImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = annottees map (_.tree) match {
// Match a class, and expand.
case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
val aliasedDefs = for {
q"@alias(..$aliases) def $tname[..$tparams](...$paramss): $tpt = $expr" <- stats
Literal(Constant(alias)) <- aliases
ident = TermName(alias.toString)
} yield {
val args = paramss map { paramList =>
paramList.map { case q"$_ val $param: $_ = $_" => q"$param" }
}
q"def $ident[..$tparams](...$paramss): $tpt = $tname(...$args)"
}
if(aliasedDefs.nonEmpty) {
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
..$aliasedDefs
}
"""
} else classDef
// Not a class.
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a class")
}
c.Expr[Any](result)
}
}
имейте в виду, что эта реализация будет хрупкой. Он только осматривает annottees, чтобы проверить, что первое-это ClassDef
. Затем он ищет членов класса, которые являются методами, аннотированными с помощью @alias
, и создает несколько сглаженных деревьев для соединения обратно в класс. Если нет аннотированных методов, он просто возвращает исходное дерево классов. Как есть, это не обнаружит повторяющиеся имена методов и удалит модификаторы (компилятор не позволит мне одновременно сопоставлять аннотации и модификаторы).
это можно легко расширить для обработки сопутствующих объектов, но я оставил их, чтобы сохранить код меньше. Вижу резюме синтаксиса quasiquotes для тех, кого я использовал. Обработка сопутствующих объектов потребует изменения result
матч для обработки case classDef :: objDef :: Nil
и objDef :: Nil
.
используется:
@aliased
class Socket {
@alias("ask", "read")
def load(n: Int): Seq[Byte] = Seq(1, 2, 3).map(_.toByte)
}
scala> val socket = new Socket
socket: Socket = Socket@7407d2b8
scala> socket.load(5)
res0: Seq[Byte] = List(1, 2, 3)
scala> socket.ask(5)
res1: Seq[Byte] = List(1, 2, 3)
scala> socket.read(5)
res2: Seq[Byte] = List(1, 2, 3)
он также может обрабатывать несколько списков параметров:
@aliased
class Foo {
@alias("bar", "baz")
def test(a: Int, b: Int)(c: String) = a + b + c
}
scala> val foo = new Foo
foo: Foo = Foo@3857a375
scala> foo.baz(1, 2)("4")
res0: String = 34