使用 Scala 宏生成方法
Use Scala macros to generate methods
我想在 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)
}
以上当然是一个滑稽的例子,但我可以看到这种技术对于自动生成 sync/async 版本的 API 或在具有大量同义词的 DSL 中很有用。是否也可以在 Scaladoc 中公开这些生成的方法?这可以使用 Scala meta 实现吗?
注意:我要问的与:https://github.com/ktoso/scala-macro-method-alias
完全不同
此外,请不要将此标记为 this 的重复,因为问题有点不同,并且在过去 3 年中 Scala 宏领域发生了很多变化。
这似乎不可能完全按照说明进行。在 class 成员上使用宏注释不允许您操作 class 本身的树。也就是说,当您使用宏注释在 class 中注释方法时,将调用 macroTransform(annottees: Any*)
,但唯一的注释者将是方法本身。
我能够使用两个注释获得概念验证。它显然不如简单地注释 class 好,但我想不出另一种解决方法。
你需要:
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
想法是,你可以用这个注解来注解每个方法,这样父class上的宏注解就可以找到你想要扩展的方法。
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
。然后,它查找 class 的成员,这些成员是用 @alias
注释的方法,并创建多个别名树以拼接回 class。如果没有带注释的方法,它只是 returns 原始的 class 树。照原样,这不会检测重复的方法名称,并去除修饰符(编译器不会让我同时匹配注释和修饰符)。
这也可以很容易地扩展以处理伴随对象,但我将它们排除在外以保持代码更小。有关我使用的匹配器,请参阅 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 = 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
我想在 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)
}
以上当然是一个滑稽的例子,但我可以看到这种技术对于自动生成 sync/async 版本的 API 或在具有大量同义词的 DSL 中很有用。是否也可以在 Scaladoc 中公开这些生成的方法?这可以使用 Scala meta 实现吗?
注意:我要问的与:https://github.com/ktoso/scala-macro-method-alias
完全不同此外,请不要将此标记为 this 的重复,因为问题有点不同,并且在过去 3 年中 Scala 宏领域发生了很多变化。
这似乎不可能完全按照说明进行。在 class 成员上使用宏注释不允许您操作 class 本身的树。也就是说,当您使用宏注释在 class 中注释方法时,将调用 macroTransform(annottees: Any*)
,但唯一的注释者将是方法本身。
我能够使用两个注释获得概念验证。它显然不如简单地注释 class 好,但我想不出另一种解决方法。
你需要:
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
想法是,你可以用这个注解来注解每个方法,这样父class上的宏注解就可以找到你想要扩展的方法。
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
。然后,它查找 class 的成员,这些成员是用 @alias
注释的方法,并创建多个别名树以拼接回 class。如果没有带注释的方法,它只是 returns 原始的 class 树。照原样,这不会检测重复的方法名称,并去除修饰符(编译器不会让我同时匹配注释和修饰符)。
这也可以很容易地扩展以处理伴随对象,但我将它们排除在外以保持代码更小。有关我使用的匹配器,请参阅 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 = 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