13

私はScalaマクロを使用していて、マクロに次のコードが含まれています。

    val fieldMemberType = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be another thing")
    }

    reify{
      new TypeBuilder() {
        type fieldType = fieldMemberType.type
      }
    }

ご覧のとおり、私はなんとかを取得できましたc.universe.Type fieldMemberType。これは、オブジェクト内の特定のフィールドのタイプを表します。TypeBuilderそれを取得したら、reifyで新しいオブジェクトを作成したいと思います。TypeBuilder抽象パラメータを持つ抽象クラスです。この抽象パラメータはfieldTypeです。fieldTypeこれを以前に見つけたタイプにしたいと思います。

ここに示すコードを実行すると、が返されますfieldMemberType not foundfieldMemberTypereify句内でを機能させる方法はありますか?

4

2 に答える 2

25

問題は、渡したコードreifyが基本的にマクロが展開されているポイントに逐語的に配置され、fieldMemberTypeそこに何も意味がないことです。

場合によってspliceは、マクロ展開時に使用している式を、具体化するコードに忍び込ませるために使用できます。たとえば、このトレイトのインスタンスを作成しようとした場合、次のようになります。

trait Foo { def i: Int }

そして、マクロ拡張時にこの変数がありました:

val myInt = 10

次のように書くことができます。

reify { new Foo { def i = c.literal(myInt).splice } }

これはここでは機能しません。つまり、ちょっとしたことを忘れreifyて、ASTを手で書き出す必要があります。残念ながら、これは頻繁に発生します。私の標準的なアプローチは、新しいREPLを開始し、次のように入力することです。

import scala.reflect.runtime.universe._

trait TypeBuilder { type fieldType }

showRaw(reify(new TypeBuilder { type fieldType = String }))

これにより、ASTの数行が吐き出され、開始点としてマクロ定義に切り取って貼り付けることができます。次に、それをいじって、次のようなものを置き換えます。

Ident(TypeBuilder)

これとともに:

Ident(newTypeName("TypeBuilder"))

そしてFINALFlag.FINALなどで。ASTタイプのメソッドが、それらをビルドするために必要なコードにより正確に対応していることを望みtoStringますが、何を変更する必要があるかをすぐに理解できます。最終的には次のようになります。

c.Expr(
  Block(
    ClassDef(
      Modifiers(Flag.FINAL),
      anon,
      Nil,
      Template(
        Ident(newTypeName("TypeBuilder")) :: Nil,
        emptyValDef,
        List(
          constructor(c),
          TypeDef(
            Modifiers(),
            newTypeName("fieldType"),
            Nil,
            TypeTree(fieldMemberType)
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  )
)

anon匿名クラス用に事前に作成した型名はどこにありますか。これconstructorは、この種のものを少し恐ろしくないものにするために使用する便利なメソッドです(この完全な作業例の最後にその定義があります)。

この式を次のようにまとめると、次のように記述できます。

scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = $1$$1@fb3f1f3

だからそれは動作します。(のtypeパラメーターc.universe.Typeからここで取得しますが、古いものとまったく同じように機能します)を取得し、それを使用して特性のタイプメンバーを定義しました。WeakTypeTagbuilderWithTypeTypeTypeBuilder

于 2012-12-10T11:09:34.153 に答える
7

ユースケースには、ツリー作成よりも簡単なアプローチがあります。確かに、木を使ってプログラムするのは本当に難しいので、私はいつも木を寄せ付けないようにするためにそれを使用しています。私は型を計算し、reifyを使用してツリーを生成することを好みます。これにより、はるかに堅牢で「衛生的な」マクロが作成され、コンパイル時のエラーが少なくなります。ツリーを使用するIMOは、ツリー変換やタプルなどのタイプのファミリーのジェネリックプログラミングなど、ごく一部の場合にのみ、最後の手段である必要があります。

ここでのヒントは、WeakTypeTagにバインドされたコンテキストを使用して、reify本体で使用する型を型パラメーターとして受け取る関数を定義することです。次に、コンテキストWeakTypeTagメソッドのおかげでユニバースタイプから構築できるWeakTypeTagsを明示的に渡すことにより、この関数を呼び出します。

したがって、あなたの場合、それは次のようになります。

  val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be            another thing")
  }

  def genRes[T: WeakTypeTag] = reify{
    new TypeBuilder() {
      type fieldType = T
    }
  }

  genRes(c.WeakTypeTag(fieldMemberType))
于 2012-12-10T23:03:48.187 に答える