2013-01-17 8 views
180

いくつかの型メンバーまたはメソッドで匿名クラスを定義するマクロを記述し、そのクラスのインスタンスを構造型として静的に作成するとします。これらの方法、等これは、2.10.0マクロシステムで可能であり、型部材の一部が非常に容易である:マクロから匿名クラスのメソッドを使用して構造型を取得する

object MacroExample extends ReflectionUtils { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.Context 

    def foo(name: String): Any = macro foo_impl 
    def foo_impl(c: Context)(name: c.Expr[String]) = { 
    import c.universe._ 

    val Literal(Constant(lit: String)) = name.tree 
    val anon = newTypeName(c.fresh) 

    c.Expr(Block(
     ClassDef(
     Modifiers(Flag.FINAL), anon, Nil, Template(
      Nil, emptyValDef, List(
      constructor(c.universe), 
      TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int])) 
     ) 
     ) 
    ), 
     Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil) 
    )) 
    } 
} 

ReflectionUtilsconstructor方法を提供convenience traitある場合。)

このマクロは、匿名クラスの型メンバーの名前を文字列リテラル:

scala> MacroExample.foo("T") 
res0: AnyRef{type T = Int} = [email protected] 

これは適切な型指定です。今、私たちはこの方法で同じことをやろうと仮定し

scala> implicitly[res0.T =:= Int] 
res1: =:=[res0.T,Int] = <function1> 

を::予想通り我々は、すべての作業を確認することができ

def bar(name: String): Any = macro bar_impl 
def bar_impl(c: Context)(name: c.Expr[String]) = { 
    import c.universe._ 

    val Literal(Constant(lit: String)) = name.tree 
    val anon = newTypeName(c.fresh) 

    c.Expr(Block(
    ClassDef(
     Modifiers(Flag.FINAL), anon, Nil, Template(
     Nil, emptyValDef, List(
      constructor(c.universe), 
      DefDef(
      Modifiers(), newTermName(lit), Nil, Nil, TypeTree(), 
      c.literal(42).tree 
     ) 
     ) 
    ) 
    ), 
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil) 
)) 
} 

しかし、我々はそれを試してみるとき、我々は得ることはありません構造タイプ:

scala> MacroExample.bar("test") 
res1: AnyRef = [email protected] 

しかし、我々はそこに余分な匿名クラスを固執場合:

def baz(name: String): Any = macro baz_impl 
def baz_impl(c: Context)(name: c.Expr[String]) = { 
    import c.universe._ 

    val Literal(Constant(lit: String)) = name.tree 
    val anon = newTypeName(c.fresh) 
    val wrapper = newTypeName(c.fresh) 

    c.Expr(Block(
    ClassDef(
     Modifiers(), anon, Nil, Template(
     Nil, emptyValDef, List(
      constructor(c.universe), 
      DefDef(
      Modifiers(), newTermName(lit), Nil, Nil, TypeTree(), 
      c.literal(42).tree 
     ) 
     ) 
    ) 
    ), 
    ClassDef(
     Modifiers(Flag.FINAL), wrapper, Nil, 
     Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil) 
    ), 
    Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil) 
)) 
} 

それは動作します:

scala> MacroExample.baz("test") 
res0: AnyRef{def test: Int} = [email protected] 

scala> res0.test 
res1: Int = 42 

これは非常に便利な、それはあなたがthisようなことを行うことができますが、例えば、しかしのためにそれが動作する理由私は理解していない、と型のメンバのバージョンは動作しますが、ないbar。私はこのmay not be defined behaviorを知っていますが、意味がありますか?マクロから構造型(その上のメソッドを持つ)を取得する、よりクリーンな方法はありますか?

+14

、マクロでそれを生成するのではなく、REPLで同じコードを記述する場合、それが動作する: スカラ> {最終的なクラスアノン{DEF X = 2}。新しいanon} res1:AnyRef {def x:Int} = anon $ 1 @ 5295c398。レポートをありがとう!私は今週見てみましょう。 –

+1

私は問題[ここ](https://issues.scala-lang.org/browse/SI-6992)を提出したことに注意してください。 –

+0

いいえ、ブロッカーではありません、ありがとう - 余分な匿名クラスのトリックは、私が必要なときはいつでも私のために働いています。私はちょうど質問のアップフォースのカップルに気づき、ステータスについて興味があった。 –

答えて

8

この質問はTravis hereによって重複して回答されています。トラッカーとEugeneのディスカッション(コメントとメーリングリスト)には、この問題へのリンクがあります。

タイプチェッカーの有名な "Skylla and Charybdis"セクションでは、英雄が暗い匿名性を逃れることを決定し、ライトを構造タイプのメンバーとして見ます。

タイプチェッカーを騙す方法はいくつかあります(オデュッセウスが羊を抱き締めるという手口を伴わない)。最も単純なのは、ブロックがインスタンス化された匿名クラスのように見えないように、ダミー文を挿入することです。

あなたが外部から参照されていない公開用語であることに気づいた場合、あなたは非公開になります。興味深いことに

object Mac { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.Context 

    /* Make an instance of a structural type with the named member. */ 
    def bar(name: String): Any = macro bar_impl 

    def bar_impl(c: Context)(name: c.Expr[String]) = { 
    import c.universe._ 
    val anon = TypeName(c.freshName) 
    // next week, val q"${s: String}" = name.tree 
    val Literal(Constant(s: String)) = name.tree 
    val A = TermName(s) 
    val dmmy = TermName(c.freshName) 
    val tree = q""" 
     class $anon { 
     def $A(i: Int): Int = 2 * i 
     } 
     val $dmmy = 0 
     new $anon 
    """ 
     // other ploys 
     //(new $anon).asInstanceOf[{ def $A(i: Int): Int }] 
     // reference the member 
     //val res = new $anon 
     //val $dmmy = res.$A _ 
     //res 
     // the canonical ploy 
     //new $anon { } // braces required 
    c.Expr(tree) 
    } 
} 
+1

私は実際に、この質問自体の最初の回避策を提供していることに気付くでしょう(ここではちょっと引用していません)。私はこの答えを質問にまとめてもらうことができてうれしく思います。バグが修正されるのを漠然と待っていたと思います。 –

+0

@TravisBrownあなたのバットベルトには他のツールもありますか?あなたのASTは "古い追加のブレーストリック"だと思っていましたが、 'new $ anon {}'のように、ClassDef/Applyが独自のブロックでラップされていないことがわかりました。私の他の奪い取りは、将来、私はquasiquotesまたは同様の特別な名前のマ​​クロで 'anon'を使用しないことです。 –

+0

q "$ {s:String}"構文は、特にあなたがパラダイスを使用している場合に少し遅れることになります。次週よりむしろ次のようになります。 –

関連する問題