1. ホーム
  2. scala

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

2022-04-20 10:31:39

質問内容

ある型のメンバやメソッドを持つ無名クラスを定義し、それらのメソッドなどを持つ構造型として静的に型付けされたそのクラスのインスタンスを作成するマクロを書きたいとします。これは 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 . 私はこれを知っている は定義されていないかもしれません。 しかし、それは何か意味があるのでしょうか?マクロから構造型(メソッド付き)を取得するもっと簡単な方法はありますか?

どのように解決するのですか?

この質問は、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)
  }
}