LoginSignup
7
3

More than 5 years have passed since last update.

scalametaでの型パラメータとコンストラクタ

Posted at

tl;dr

直接、annotation classの型パラメータやコンストラクタにはアクセス出来ない。
その代わりに、thisを用いてパターンマッチで取得することが出来る。

題材

mix-in injectionとかminimal cake patternと呼ばれる、DI対象となるclassをmix-inするmacro annotationを実装した。

  • Uses[MyService]とするとval myService: MyServiceがフィールドに追加される
  • MixIn[MyService](new MyServiceImpl)するとval myService: MyService = new MyServiceImplがフィールドに追加される

こんな感じで使用する。

trait MyService { def double(n: Int) = n * 2 }
object MyServiceImpl extends MyService

trait OtherService { def triple(n: Int) = n * 3 }
class OtherServiceImpl extends OtherService

@Uses[MyService]
@Uses[OtherService]
trait UsesFieldTarget {
  def showDouble(n: Int) = println(this.myService.double(n))
  def showTriple(n: Int) = println(this.otherService.triple(n))
}

@MixIn[MyService](MyServiceImpl)
@MixIn[OtherService](new OtherServiceImpl)
class UsesFieldTargetImpl extends UsesFieldTarget

object UsesFieldApp extends App {
  val impl = new UsesFieldTargetImpl
  impl.showDouble(100) // 200
  impl.showTriple(100) // 300
}

annotationの実装はscalameta-prac/uses.scalaで、
サンプルコードはscalameta-prac/UsesApp.scala

ちなみに、IntelliJ上ではうまく解決出来ておらずMacro expansion failedとか表示されるし、補完もされない...。
こんな感じ。。

スクリーンショット

このような話もあるので期待したい。
IntelliJ IDEA 2016.3 RC: Scala.Js, Scala.Meta and More | IntelliJ Scala plugin blog

macro annotationで型パラメータと引数を処理する

上のような使い方をするには型パラメータと引数を処理しなければならないが、inline macroの場合はちょっと特殊。

型パラメータを取得する

class Uses[T] extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta { ??? }
}

このようなUses[T]において、Tには直接アクセス出来ない。
thisを用いるとTTerm.ApplyTypeとして取得できる。

class Uses[T] extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    val typeParam: String = 
      this match {
        case Term.New(Template(_, 
            Seq(Term.Apply(
              Term.ApplyType(
                _, 
                Seq(Type.Name(name: String))
              ), _)), _, _)) =>
            name
        case _ => None
      }
      ???
  }
}

これで型パラメータとして渡されたTのコンパイル時における型名が文字列として手に入る。
つまりUses[MyService]みたいになっているとMyServiceという文字列が取れる。

コンストラクタを処理する

Uses[T]でねじ込んだT型のフィールドに実装を与えるMixIn[T](t: T)を作る。
そのためにはコンストラクタを手に入れる必要がある。
これも型パラメータと同様にthisに対するパターンマッチで取り出す。

型パラメータとコンストラクタをそれぞれ取り出すパターンマッチは以下のように書ける。

this match {
  case Term.New(Template(_, Seq(
    Term.Apply(
      Term.ApplyType(_, Seq(Type.Name(name: String))),
      Seq(implArg: Term.Arg)
    )
  ), _, _)) =>
    (name, implArg)

Term.Applyに与えられる引数のTerm.ApplyTypeSeq[Type.Arg]として型パラメータとコンストラクタが得られる。
これでMixIn[T](t: T)Ttが無事に得られる。

ちなみに、引数を渡すから型パラメータは型推論で解決してくれるだろう、と思ってもダメ。

Error:wrong number of type arguments for MixIn, should be 1

みたいな型パラメータが足りませんよ、というエラーが出てしまう。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3