マクロから匿名クラスのメソッドを持つ構造型を取得する


181

いくつかの型のメンバーまたはメソッドで匿名クラスを定義し、それらのメソッドなどで構造型として静的に型指定されたクラスのインスタンスを作成するマクロを作成するとします。これは、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)
    ))
  }
}

(私の方法を提供ReflectionUtilsする便利な特性はどこにありますconstructor。)

このマクロを使用すると、匿名クラスの型メンバーの名前を文字列リテラルとして指定できます。

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6

適切に入力されていることに注意してください。すべてが期待どおりに機能していることを確認できます。

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 = $1$$1@da12492

しかし、そこに余分な匿名クラスを挿入すると、次のようになります。

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} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

これは非常に便利です。たとえば、このようなことを実行できますが、なぜ機能するのか、タイプメンバーバージョンは機能しますが、機能しませんbar。これは定義された動作はない可能性があることを知っていますが、それは意味がありますか?マクロから構造タイプ(メソッドが含まれている)を取得するためのより明確な方法はありますか?


14
興味深いことに、同じコードをマクロで生成する代わりにREPLで作成すると、機能します。scala> {final class anon {def x = 2}; 新しいanon} res1:AnyRef {def x:Int} = anon $ 1 @ 5295c398。レポートをありがとう!今週見ていきます。
Eugene Burmako 2013年

1
ここで問題を報告したことに注意してください
Travis Brown

いいえ、ブロッカーではありません。感謝します。必要なときはいつでも、余分な匿名クラスのトリックがうまくいきました。私は質問に対するいくつかの賛成票に気づき、ステータスについて知りました。
Travis Brown

3
タイプメンバー部分は非常に簡単です-> wTF?あなたは非常にひび割れています!もちろん良い方法です:)
ZaoTaoBao

3
ここには153の賛成票があり、scala-lang.org問題には 1つだけです。賛成票が多いほど、解決が速くなる可能性がありますか?
moodboom 2013年

回答:


9

この質問は、Travisによってここで重複して回答されています。トラッカーに問題へのリンクと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)
  }
}

2
私が実際にこの質問自体で最初の回避策を提供していることに注意します(ここでは、準引用符で囲まれていません)。この回答で質問を締めくくることができてうれしいです。バグが修正されるまで漠然と待っていたと思います。
Travis Brown

@TravisBrownバットベルトにも他のツールがあると思います。頭の方のThx:私はあなたのASTが「古い余分な中かっこトリック」であると想定しましたが、ClassDef / Applyが独自のブロックにラップされていないことがわかりますnew $anon {}。私のもう1 anonつの要点は、将来、準引用符または同様の特別な名前を持つマクロで使用しないことです。
som-snytt 2013年

q "$ {s:String}"構文は、特に楽園を使用している場合、少し遅れます。来週よりも来月のほうがいいです。
Denys Shabalin 2013年

@ som-snytt @ denys-shabalin、構造タイプa-laには特別なトリックがありshapeless.Genericますか?Auxパターンの戻り値の型を強制するという私の最善の意図にもかかわらず、コンパイラーは構造型を介して見ることを拒否します。
フラビアン2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.