2015-10-22 14 views
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のメタを使用してこれが可能ですか?

注:質問は少し異なっており、多くが過去にScalaのマクロ土地に変更されたとしてもthisの重複としてこれをマークしないでくださいhttps://github.com/ktoso/scala-macro-method-alias

:私は何を求めていますが全く異なります3年。

+0

quasiquotesを使ってなんとかようです。 Macrosに関する最近のミートアップからこのスライドデッキをチェックしてください:https://speakerdeck.com/bwmcadams/ny-scala-meetup-scala-macros-or-how-i-learned-to-stop-worrying-and-mumble- wtf – JoseM

答えて

8

これは、正確に述べられているようには見えません。クラスメンバでマクロ注釈を使用しても、クラス自体のツリーを操作することはできません。つまり、マクロ注釈を持つクラス内のメソッドに注釈を付けると、macroTransform(annottees: Any*)が呼び出されますが、注釈だけがメソッドそのものになります。

私は2つの注釈を使って概念実証を行うことができました。明らかにクラスに注釈を付けるだけではうまくいきませんが、別の方法で考えることはできません。

あなたは必要があります

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

何らかの理由で '!'メソッドがコピーされましたが、動作しませんでした。私はそれが特殊文字なので何らかの特別な扱いが必要だと思うが、私は現時点でその理由を正確に知る時間がない。 –

関連する問題