Posted at

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

More than 1 year has passed since last update.


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


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