7

Я хочу генерировать псевдонимы методов с использованием макросов аннотации в Scala 2.11+. Я даже не уверен, что это возможно. Если да, то как?Использовать макросы Scala для генерации методов

Пример - Учитывая это ниже, я хочу аннотаций макросы для расширения в

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 или DSL с синхронным или асинхронным доступом с большим количеством синонимов. Можно ли также выставлять эти сгенерированные методы в Scaladoc? Возможно ли это с помощью Scala meta?

Примечание: То, что я спрашиваю довольно сильно отличается от: https://github.com/ktoso/scala-macro-method-alias

Также, пожалуйста, не отмечайте это как дубликат this как вопрос немного отличается, и многое изменилось в Scala макросъемки земли в прошлом 3 года.

+0

Это кажется выполнимым с помощью quasiquotes. Посмотрите эту слайд-колоду из недавней встречи о макросах: https://speakerdeck.com/bwmcadams/ny-scala-meetup-scala-macros-or-how-i-learned-to-stop-worrying-and-mumble- wtf – JoseM

ответ

8

Это не представляется возможным, как указано. Использование аннотации макроса для члена класса не позволяет вам манипулировать деревом самого класса. То есть, когда вы комментируете метод внутри класса с аннотацией макроса, будет вызываться 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) 
    } 

} 

Имейте в виду, что эта реализация будет хрупким. Он проверяет только аннота, чтобы проверить, что первый - ClassDef. Затем он ищет членов класса, которые являются методами, аннотированными с помощью @alias, и создает несколько деревьев с псевдонимом для сращивания обратно в класс. Если нет аннотированных методов, он просто возвращает исходное дерево классов. Как и в случае, это не будет определять повторяющиеся имена методов и удаляет модификаторы (компилятор не позволит мне одновременно сопоставлять аннотации и модификаторы).

Это можно легко расширить, чтобы обрабатывать объекты-компаньоны, но я оставил их, чтобы уменьшить код. См. quasiquotes syntax summary для тех, которые я использовал. Для обработки сопутствующих объектов потребуется изменить соответствие 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 = [email protected] 

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 = [email protected] 

scala> foo.baz(1, 2)("4") 
res0: String = 34 
+0

По какой-то причине метод '!' был скопирован, но не работает. Я подозреваю, потому что это особый персонаж и нуждается в каком-то особом обращении, но на данный момент у меня нет времени, чтобы узнать, почему именно. –

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