使用Scala宏生成方法 [英] Use Scala macros to generate methods

查看:145
本文介绍了使用Scala宏生成方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用Scala 2.11+中的注释宏生成方法的别名.我什至不确定那是否有可能.如果是,怎么办?

I want to generate aliases of methods using annotation macros in Scala 2.11+. I am not even sure that is even possible. If yes, how?

示例-鉴于此,我希望注释宏扩展为

Example - Given this below, I want the annotation macros to expand into

class Socket {
  @alias(aliases = Seq("!", "ask", "read"))
  def load(n: Int): Seq[Byte] = {/* impl */}
}

我希望以上代码生成同义词方法存根,如下所示:

I want the above to generate the synonym method stubs as follows:

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,这有可能吗?

The above is of course a facetious example but I can see this technique being useful to auto generate sync/async versions of APIs or in DSLs with lots of synonyms. Is it possible to also expose these generated methods in the Scaladoc too? Is this something possible using Scala meta?

注意:我要问的与以下内容完全不同: https://github. com/ktoso/scala-macro-method-alias

Note: What I am asking is quite different from: https://github.com/ktoso/scala-macro-method-alias

也请不要将此标记为 this 的重复项问题有点不同,在过去3年中,Scala宏土地发生了很多变化.

Also please don't mark this as a duplicate of this as the question is a bit different and a lot has changed in Scala macro land in past 3 years.

推荐答案

这似乎与所陈述的完全不同.在类成员上使用宏注释不允许您操纵类本身的树.也就是说,当您在带有宏注释的类中对方法进行注释时,将调用macroTransform(annottees: Any*),但是唯一的注释者将是方法本身.

This doesn't seem possible exactly as stated. Using a macro annotation on a class member does not allow you to manipulate the tree of the class itself. That is, when you annotate a method within a class with a macro annotation, macroTransform(annottees: Any*) will be called, but the only annottee will be the method itself.

我能够使用两个注释获得概念验证.显然,它不如简单地对类进行注释那样好,但是我想不出另一种解决方法.

I was able to get a proof-of-concept working with two annotations. It's obviously not as nice as simply annotating the class, but I can't think of another way around it.

您需要:

import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context

想法是,您可以使用此注释来注释每个方法,以便父类上的宏注释能够找到您要扩展的方法.

The idea is, you can annotate each method with this annotation, so that a macro annotation on the parent class is able to find which methods you want to expand.

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注释的方法的类的成员,并创建多个别名树以将其拼接回该类.如果没有带注释的方法,则只返回原始的类树.照原样,这将不会检测到重复的方法名称,并且会删除修饰符(编译器不会让我同时匹配注释和修饰符).

Keep in mind this implementation will be brittle. It only inspects the annottees to check that the first is a ClassDef. Then, it looks for members of the class that are methods annotated with @alias, and creates multiple aliased trees to splice back into the class. If there are no annotated methods, it simply returns the original class tree. As is, this will not detect duplicate method names, and strips away modifiers (the compiler would not let me match annotations and modifiers at the same time).

这也可以轻松地扩展为处理伴随对象,但是我省略了它们以保持代码更小.有关我使用的匹配器,请参见准引用语法摘要.处理伴侣对象需要修改result匹配项以处理case classDef :: objDef :: Nil和大小写objDef :: Nil.

This can easily be expanded to handle companion objects as well, but I left them out to keep the code smaller. See the quasiquotes syntax summary for the matchers I used. Handling companion objects would require modifying the result match to handle case classDef :: objDef :: Nil, and case 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)

它还可以处理多个参数列表:

It can also handle multiple parameter lists:

@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

这篇关于使用Scala宏生成方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆