scalametaでの型パラメータとコンストラクタ
2017-02-19
QiitaScalametaprogrammingmetatl;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
を用いるとT
がTerm.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.ApplyType
とSeq[Type.Arg]
として型パラメータとコンストラクタが得られる。
これでMixIn[T](t: T)
のT
とt
が無事に得られる。
ちなみに、引数を渡すから型パラメータは型推論で解決してくれるだろう、と思ってもダメ。
Error:wrong number of type arguments for MixIn, should be 1
みたいな型パラメータが足りませんよ、というエラーが出てしまう。
from: https://qiita.com/petitviolet/items/c751b52df53b0f07da4c