Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

meta programming Scala

petitviolet
September 09, 2017

meta programming Scala

Metaprogramming Scala using scala.meta.
performed presentation as Scala-Kansai Summit 2017.

petitviolet

September 09, 2017
Tweet

More Decks by petitviolet

Other Decks in Programming

Transcript

  1. 自己紹介 • 小紫 弘貴(Hiroki Komurasaki) • インターネットだと@petitviolet • Scala歴2年くらい •

    でScalaとかGo書いてます • https://www.facebook.com/fringeneer/ • http://fringeneer.hatenablog.com/ 
  2. 今日話すこと • メタプログラミングってなに? • Scalaで出来るメタプログラミング • scala.meta • 使い方イメージ •

    ユースケース・実装例 • 実装方法 • scalametaの内部的な話 • ロードマップ 
  3. 

  4. よく使われるリフレクションもメタプログラミ ングの一種。 実行時/コンパイル時のオブジェクトにアクセス 出来る。  try { Class<?> cl =

    Class.forName("Foo"); Method method = cl.getMethod("hello"); method.invoke(cl.newInstance()); } catch (Exception e) { e.printStackTrace(); }
  5. よく使われるリフレクションもメタプログラミ ングの一種。 実行時/コンパイル時のオブジェクトにアクセス 出来る。  import scala.reflect.runtime.{universe => ru} def

    getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] val l = List(1, 2, 3) val head: ru.Symbol = getTypeTag(l).tpe.decl(ru.TermName("head")) val lMirror: ru.InstanceMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(l) lMirror.reflectMethod(head.asMethod).apply() // 1
  6. よく使われるリフレクションもメタプログラミ ングの一種。 実行時/コンパイル時のオブジェクトにアクセス 出来る。  import scala.reflect.runtime.{universe => ru} def

    getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] val l = List(1, 2, 3) val head: ru.Symbol = getTypeTag(l).tpe.decl(ru.TermName("head")) val lMirror: ru.InstanceMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(l) lMirror.reflectMethod(head.asMethod).apply() // 1
  7. 

  8. • def macro • 引数等のメタ情報を利用できる • macro annotation • annotationをつけたクラスやメソッドのメタ情報、例

    えば型情報が利用できる • implicit macro • implicit parameterの自動生成 • type provider • 型そのものを自動生成 • 構造的部分型 • 他にもいろいろ • マクロバンドルとか抽出子マクロとか  http://docs.scala-lang.org/ja/overviews/macros/usecases.html
  9. • def macro • 引数等のメタ情報を利用できる • macro annotation • annotationをつけたクラスやメソッドのメタ情報、例

    えば型情報が利用できる • implicit macro • implicit parameterの自動生成 • type provider • 型そのものを自動生成 • 構造的部分型 • 他にもいろいろ • マクロバンドルとか抽出子マクロとか  http://docs.scala-lang.org/ja/overviews/macros/usecases.html
  10. def macro • Scalaマクロの代表格(要出典  object DefMacro { def show[A](value:

    A): String = macro DefMacroImpl.showImpl[A] } object DefMacroImpl { def showImpl[A: c.WeakTypeTag](c: blackbox.Context) (value: c.Expr[A]): c.Expr[String] = { import c.universe._ val typeName = c.weakTypeTag[A].tpe.typeSymbol.fullName val tree = value.tree c.Expr[String](Literal(Constant(s"type: $typeName, value: $tree"))) } }
  11. def macro • Scalaマクロの代表格(要出典  object DefMacro { def show[A](value:

    A): String = macro DefMacroImpl.showImpl[A] } object DefMacroImpl { def showImpl[A: c.WeakTypeTag](c: blackbox.Context) (value: c.Expr[A]): c.Expr[String] = { import c.universe._ val typeName = c.weakTypeTag[A].tpe.typeSymbol.fullName val tree = value.tree c.Expr[String](Literal(Constant(s"type: $typeName, value: $tree"))) } }
  12. def macro • Scalaマクロの代表格(要出典  object DefMacro { def show[A](value:

    A): String = macro DefMacroImpl.showImpl[A] } object DefMacroImpl { def showImpl[A: c.WeakTypeTag](c: blackbox.Context) (value: c.Expr[A]): c.Expr[String] = { import c.universe._ val typeName = c.weakTypeTag[A].tpe.typeSymbol.fullName val tree = value.tree c.Expr[String](Literal(Constant(s"type: $typeName, value: $tree"))) } }
  13.  object DefMacro { def show[A](value: A): String = macro

    DefMacroImpl.showImpl[A] } object DefMacroImpl { def showImpl[A: c.WeakTypeTag](c: blackbox.Context) (value: c.Expr[A]): c.Expr[String] = { import c.universe._ val typeName = c.weakTypeTag[A].tpe.typeSymbol.fullName val tree = value.tree c.Expr[String](Literal(Constant(s"type: $typeName, value: $tree"))) } } println(DefMacro.show[Int](100)) // type: scala.Int, value: 100 println(DefMacro.show[Long](100L)) // type: scala.Long, value: 100L println(DefMacro.show(new java.lang.String("hello"))) // type: java.lang.String, value: new java.lang.String("hello")
  14.  def log(foo: String)(implicit line: sourcecode.Line, file: sourcecode.File) = {

    println(s"${file.value}:${line.value} $foo") } log("Foooooo") // sourcecode/shared/src/test/scala/sourcecode/Tests.scala:86 Foooooo lihaoyi/sourcecode • ソースコードの行番号やファイル名を取得 • プログラムをメタに扱っている • 内部実装はdef macroを使用
  15. 

  16. 

  17. scalameta? > Scalameta is a clean-room implementation of a metaprogramming

    toolkit for Scala, designed to be simple, robust and portable. > We are striving for scalameta to become a successor of scala.reflect, the current de facto standard in the Scala ecosystem. http://scalameta.org/ 
  18. scalameta? > Scalameta is a clean-room implementation of a metaprogramming

    toolkit for Scala, designed to be simple, robust and portable. > We are striving for scalameta to become a successor of scala.reflect, the current de facto standard in the Scala ecosystem. http://scalameta.org/ 
  19. scalameta? > Scalameta is a clean-room implementation of a metaprogramming

    toolkit for Scala, designed to be simple, robust and portable. > We are striving for scalameta to become a successor of scala.reflect, the current de facto standard in the Scala ecosystem. http://scalameta.org/ 
  20. scalameta? > Scalameta is a clean-room implementation of a metaprogramming

    toolkit for Scala, designed to be simple, robust and portable. > We are striving for scalameta to become a successor of scala.reflect, the current de facto standard in the Scala ecosystem. http://scalameta.org/ 
  21. scalameta? > Scalameta is a modern metaprogramming library for Scala

    that supports a wide range of language versions and execution platforms. > Originally, Scalameta was founded to become a better macro system for Scala, but over time we shifted focus to developer tools and spun off the new macro system into a separate project. http://scalameta.org/ 
  22. 

  23. 

  24. 

  25. 

  26. 

  27. 

  28.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } }
  29.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } scala.metaͷimport
  30.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } annotation༻ͷclassͰ͢Αએݴ
  31.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } ࣮ߦ͞ΕΔapplyؔ਺
  32.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } annotationͨ͠ର৅
  33.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } ASTͷॻ͖׵͑
  34.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } ৽͍͠AST
  35.  import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.meta._ @compileTimeOnly("hello") class hello extends

    StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } } def͡Όͳ͚Ε͹ࣦഊ
  36.  @hello def say() = { println("world") } class hello

    extends StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } }
  37.  @hello def say() = { println("world") } class hello

    extends StaticAnnotation { inline def apply(defn: Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } }
  38.  object TimeLoggingApp extends App { def heavy(n: Long): Long

    = { Thread.sleep(n) n } println(heavy(100)) }
  39.  object TimeLoggingApp extends App { @TimeLogging def heavy(n: Long):

    Long = { Thread.sleep(n) n } println(heavy(100)) }
  40.  [heavy]tracking time:101 ms 100 object TimeLoggingApp extends App {

    @TimeLogging def heavy(n: Long): Long = { Thread.sleep(n) n } println(heavy(100)) }
  41.  class TimeLogging extends StaticAnnotation { inline def apply(defn: Any):

    Any = meta { defn match { case d @ Defn.Def(_, name, _, _, _, body) => def s = Lit.String.apply _ val newBody = q""" val start = System.nanoTime() val result = $body val end = System.nanoTime() println(${s(s"[${name.value}]tracking time:")} + ((end - start) / ${Lit.Long(1000000)}) + ${s(" ms")}) result """ d.copy(body = newBody) case _ => abort("annotate only function!") } } }
  42.  class TimeLogging extends StaticAnnotation { inline def apply(defn: Any):

    Any = meta { defn match { case d @ Defn.Def(_, name, _, _, _, body) => def s = Lit.String.apply _ val newBody = q""" val start = System.nanoTime() val result = $body val end = System.nanoTime() println(${s(s"[${name.value}]tracking time:")} + ((end - start) / ${Lit.Long(1000000)}) + ${s(" ms")}) result """ d.copy(body = newBody) case _ => abort("annotate only function!") } } } લޙͰ࣌ؒऔಘ
  43.  class TimeLogging extends StaticAnnotation { inline def apply(defn: Any):

    Any = meta { defn match { case d @ Defn.Def(_, name, _, _, _, body) => def s = Lit.String.apply _ val newBody = q""" val start = System.nanoTime() val result = $body val end = System.nanoTime() println(${s(s"[${name.value}]tracking time:")} + ((end - start) / ${Lit.Long(1000000)}) + ${s(" ms")}) result """ d.copy(body = newBody) case _ => abort("annotate only function!") } } } ग़ྗ͍ͯ͠Δ͚ͩ
  44.  @Length(min = 1, max = 10) val ok =

    1 to 10 @Length(min = 100) val ng = 1 to 10
  45.  requirement failed: ng size is invalid. actual: 10, min:

    100 @Length(min = 1, max = 10) val ok = 1 to 10 // OK @Length(min = 100) val ng = 1 to 10 // NG
  46.  q""" def msg(min: Int, max: Int, resultSize: Int): String

    = { val minCond = { if (min <= 0) "" else "min: " + min } val maxCond = { if (max <= 0) "" else "max: " + max } val res = ${s(name.syntax)} + " size is invalid. " + "actual: " + resultSize + ", " + minCond + maxCond res } def minValid(min: Int, resultSize: Int): Boolean = if (min <= 0) true else min <= resultSize def maxValid(max: Int, resultSize: Int): Boolean = if (max <= 0) true else max >= resultSize val result = $term val size: Int = result.size require(minValid(${i(min)}, size) && maxValid(${i(max)}, size), msg(${i(min)}, ${i(max)}, size)) result """
  47.  q""" def msg(min: Int, max: Int, resultSize: Int): String

    = { val minCond = { if (min <= 0) "" else "min: " + min } val maxCond = { if (max <= 0) "" else "max: " + max } val res = ${s(name.syntax)} + " size is invalid. " + "actual: " + resultSize + ", " + minCond + maxCond res } def minValid(min: Int, resultSize: Int): Boolean = if (min <= 0) true else min <= resultSize def maxValid(max: Int, resultSize: Int): Boolean = if (max <= 0) true else max >= resultSize val result = $term val size: Int = result.size require(minValid(${i(min)}, size) && maxValid(${i(max)}, size), msg(${i(min)}, ${i(max)}, size)) result """ ৚݅൑ఆΛࠩ͠ࠐΜͰ͍Δ requireͳͷͰ࣮ߦ࣌ྫ֎
  48.  class MyClass(val n: Int, val s: String) object UnapplyApp

    extends App { val target = new MyClass(n = 100, s = "hoge") }
  49.  @Unapply class MyClass(val n: Int, val s: String) object

    UnapplyApp extends App { val target = new MyClass(n = 100, s = "hoge") }
  50.  @Unapply class MyClass(val n: Int, val s: String) object

    UnapplyApp extends App { val target = new MyClass(n = 100, s = "hoge") target match { case MyClass(n, s) => println(s"$n, $s") case _ => sys.error("out!") } }
  51.  100, hoge @Unapply class MyClass(val n: Int, val s:

    String) object UnapplyApp extends App { val target = new MyClass(n = 100, s = "hoge") target match { case MyClass(n, s) => println(s"$n, $s") case _ => sys.error("out!") } }
  52.  @compileTimeOnly("not expanded") class Unapply extends scala.annotation.StaticAnnotation { inline def

    apply(defn: Any): Any = meta { defn match { // companion object exists case Term.Block( Seq(cls @ Defn.Class(_, name, _, ctor, _), companion: Defn.Object)) => val newCompanion = Unapply.insert(cls)(Some(companion)) Term.Block(Seq(cls, newCompanion)) // companion object does not exists case cls @ Defn.Class(_, name, _, ctor, _) => val newCompanion = Unapply.insert(cls)(None) Term.Block(Seq(cls, newCompanion)) case _ => println(defn.structure) abort("@Unapply must annotate a class.") } } } object Unapply extends CompanionMethodHelper { override protected val METHOD_NAME: String = "unapply" override protected def create(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Def = { val (name, paramss) = (cls.name, cls.ctor.paramss) val argName = Term.Name("arg") val inputParam: Seq[Seq[Term.Param]] = { val param: Term.Param = Term.Param(Nil, argName, Some(name), None) (param :: Nil) :: Nil } val resultParam: Type = { val types: Seq[Type] = paramss.flatMap { _.collect { case Term.Param(_ :: Mod.ValParam() :: Nil, _, Some(typeArg), _) => // private val n: Int Type.Name(typeArg.syntax) case Term.Param(_ :: Mod.VarParam() :: Nil, _, Some(typeArg), _) => // private var n: Int Type.Name(typeArg.syntax) case Term.Param(Mod.ValParam() :: Nil, _, Some(typeArg), _) => // val n: Int Type.Name(typeArg.syntax) case Term.Param(Mod.VarParam() :: Nil, _, Some(typeArg), _) => // var n: Int Type.Name(typeArg.syntax) case x => println(s"invalid paramss: $paramss") abort(s"non-accessible constructor exists. `unapply` always returns None. cause => $x") }} val tupled = Type.Tuple(types) Type.Apply(Type.Name("Option"), tupled :: Nil) } val body: Term = { val select: Seq[Term.Select] = paramss.flatMap { _.map { param => Term.Select(argName, Term.Name(param.name.value)) } } Term.Block(q"Some((..$select))" :: Nil) } Defn.Def(Nil, Term.Name("unapply"), Nil, inputParam, Some(resultParam), body) } } private[petitviolet] trait CompanionMethodHelper { protected val METHOD_NAME: String protected def create(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Def def insert(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Object = { def method = create(cls)(companionOpt) companionOpt map { companion => val stats = companion.templ.stats getOrElse Nil if (alreadyDefined(stats)) companion else companion.copy(templ = companion.templ.copy(stats = Some(method +: stats))) } getOrElse { q"object ${Term.Name(cls.name.value)} { $method }" } } private def alreadyDefined(stats: Seq[Stat]): Boolean = stats.exists { _.syntax.contains(s"def $METHOD_NAME") } }
  53.  class Unapply extends scala.annotation.StaticAnnotation { inline def apply(defn: Any):

    Any = meta { defn match { // companion object exists case Term.Block( Seq(cls @ Defn.Class(_, name, _, ctor, _), companion: Defn.Object)) => val newCompanion = Unapply.insert(cls)(Some(companion)) Term.Block(Seq(cls, newCompanion)) // companion object does not exists case cls @ Defn.Class(_, name, _, ctor, _) => val newCompanion = Unapply.insert(cls)(None) Term.Block(Seq(cls, newCompanion)) case _ => println(defn.structure) abort("@Unapply must annotate a class.") } } }
  54.  class Unapply extends scala.annotation.StaticAnnotation { inline def apply(defn: Any):

    Any = meta { defn match { // companion object exists case Term.Block( Seq(cls @ Defn.Class(_, name, _, ctor, _), companion: Defn.Object)) => val newCompanion = Unapply.insert(cls)(Some(companion)) Term.Block(Seq(cls, newCompanion)) // companion object does not exists case cls @ Defn.Class(_, name, _, ctor, _) => val newCompanion = Unapply.insert(cls)(None) Term.Block(Seq(cls, newCompanion)) case _ => println(defn.structure) abort("@Unapply must annotate a class.") } } } ίϯύχΦϯΦϒδΣΫτ͕͋Ε͹ Classఆ͕ٛTerm.BlockʹͳΔ ίϯύχΦϯΦϒδΣΫτ͕ͳ͚Ε͹ ClassఆٛͷΈ
  55.  @compileTimeOnly("not expanded") class Unapply extends scala.annotation.StaticAnnotation { inline def

    apply(defn: Any): Any = meta { defn match { // companion object exists case Term.Block( Seq(cls @ Defn.Class(_, name, _, ctor, _), companion: Defn.Object)) => val newCompanion = Unapply.insert(cls)(Some(companion)) Term.Block(Seq(cls, newCompanion)) // companion object does not exists case cls @ Defn.Class(_, name, _, ctor, _) => val newCompanion = Unapply.insert(cls)(None) Term.Block(Seq(cls, newCompanion)) case _ => println(defn.structure) abort("@Unapply must annotate a class.") } } } object Unapply extends CompanionMethodHelper { override protected val METHOD_NAME: String = "unapply" override protected def create(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Def = { val (name, paramss) = (cls.name, cls.ctor.paramss) val argName = Term.Name("arg") val inputParam: Seq[Seq[Term.Param]] = { val param: Term.Param = Term.Param(Nil, argName, Some(name), None) (param :: Nil) :: Nil } val resultParam: Type = { val types: Seq[Type] = paramss.flatMap { _.collect { case Term.Param(_ :: Mod.ValParam() :: Nil, _, Some(typeArg), _) => // private val n: Int Type.Name(typeArg.syntax) case Term.Param(_ :: Mod.VarParam() :: Nil, _, Some(typeArg), _) => // private var n: Int Type.Name(typeArg.syntax) case Term.Param(Mod.ValParam() :: Nil, _, Some(typeArg), _) => // val n: Int Type.Name(typeArg.syntax) case Term.Param(Mod.VarParam() :: Nil, _, Some(typeArg), _) => // var n: Int Type.Name(typeArg.syntax) case x => println(s"invalid paramss: $paramss") abort(s"non-accessible constructor exists. `unapply` always returns None. cause => $x") }} val tupled = Type.Tuple(types) Type.Apply(Type.Name("Option"), tupled :: Nil) } val body: Term = { val select: Seq[Term.Select] = paramss.flatMap { _.map { param => Term.Select(argName, Term.Name(param.name.value)) } } Term.Block(q"Some((..$select))" :: Nil) } Defn.Def(Nil, Term.Name("unapply"), Nil, inputParam, Some(resultParam), body) } } private[petitviolet] trait CompanionMethodHelper { protected val METHOD_NAME: String protected def create(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Def def insert(cls: Defn.Class)(companionOpt: Option[Defn.Object]): Defn.Object = { def method = create(cls)(companionOpt) companionOpt map { companion => val stats = companion.templ.stats getOrElse Nil if (alreadyDefined(stats)) companion else companion.copy(templ = companion.templ.copy(stats = Some(method +: stats))) } getOrElse { q"object ${Term.Name(cls.name.value)} { $method }" } } private def alreadyDefined(stats: Seq[Stat]): Boolean = stats.exists { _.syntax.contains(s"def $METHOD_NAME") } } • ίϯετϥΫλ͔ΒϑΟʔϧυ໊Λऔಘ • unapplyͷ࣮૷Ͱඞཁ • ίϯύχΦϯΦϒδΣΫτ͕ఆٛࡁΈ͔Ͳ͏͔ ΛνΣοΫ • ͋Ε͹unapplyͷ௥Ճ • ͳ͚Ε͹unapplyΛ࣋ͭίϯύχΦϯΦϒ δΣΫτΛఆٛ • annotationͨ͠ΫϥεΛίϯύχΦϯΦϒδΣ ΫτΛ࣋ͭΫϥεͰஔ͖׵͑Δ
  56.  trait MyService { def double(i: Int): Int = i

    * 2 } object MyServiceApp extends App { }
  57.  trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  58.  trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  59.  trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  60.  200 trait MyService { def double(i: Int): Int =

    i * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  61.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } // to inject a field has implementation val addField = MixIn.implementationToAdd(injectionType, impl) defn match { case [email protected](_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) cls.copy(templ = template.copy(stats = Some(templateStats))) case [email protected](_, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) obj.copy(templ = template.copy(stats = Some(templateStats))) case [email protected](_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) trt.copy(templ = template.copy(stats = Some(templateStats))) case _ => println(this.structure) abort("invalid annotating") } } } object MixIn { private[metas] def implementationToAdd(typeName: Type.Name, impl: Term): Defn.Val = { val fieldName = { val _tpeName = typeName.value val valName = _tpeName.head.toLower + _tpeName.tail Pat.Var.Term(Term.Name(valName)) } q"val $fieldName: $typeName = $impl" } }
  62.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } // to inject a field has implementation val addField = MixIn.implementationToAdd(injectionType, impl) defn match { case [email protected](_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) cls.copy(templ = template.copy(stats = Some(templateStats))) case [email protected](_, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) obj.copy(templ = template.copy(stats = Some(templateStats))) case [email protected](_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) trt.copy(templ = template.copy(stats = Some(templateStats))) case _ => println(this.structure) abort("invalid annotating") } } } object MixIn { private[metas] def implementationToAdd(typeName: Type.Name, impl: Term): Defn.Val = { val fieldName = { val _tpeName = typeName.value val valName = _tpeName.head.toLower + _tpeName.tail Pat.Var.Term(Term.Name(valName)) } q"val $fieldName: $typeName = $impl" } } • annotationʹର͢ΔܕύϥϝʔλͱίϯετϥΫ λͷtreeΛऔಘ • annotationͨ͠Ϋϥεʹରͯ͠ϑΟʔϧυΛ௥Ճ • ϑΟʔϧυͷܕͱbodyʹલड़ͷ஋Λ༩͑Δ
  63. 

  64.  200 trait MyService { def double(i: Int): Int =

    i * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  65.  trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  66.  class MixIn[T](impl: T) extends StaticAnnotation { inline def apply(defn:

    Any): Any = meta { println(s"structure: ${this.structure}") defn } } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  67.  class MixIn[T](impl: T) extends StaticAnnotation { inline def apply(defn:

    Any): Any = meta { println(s"structure: ${this.structure}") defn } } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  68. 

  69.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } …
  70.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } … thisʹର͢ΔύλʔϯϚον ίϯετϥΫλͷ`impl`͸௚઀औΕͳ͍
  71.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } … ܕύϥϝʔλɾίϯετϥΫλΛऔಘ
  72.  @compileTimeOnly("@MixIn not expanded") class MixIn[T](impl: T) extends scala.annotation.StaticAnnotation {

    inline def apply(defn: Any): Any = meta { val (injectionType: Type.Name, impl: Term) = this match { case Term.New(Template(_, Seq( Term.Apply( Term.ApplyType(_, Seq(typeName)), Seq(implArg: Term.Arg) )), _, _)) => (typeName, implArg) case _ => println(this.structure) abort("invalid parameters") } … ͏·͍͔͘ͳ͍࣌͸ println(this.structure)͢Δͱྑ͍ ίϯύΠϧ࣌ͷϝοηʔδͱͯ͠දࣔ͞ΕΔ
  73. 既存のマクロの問題点 • macro実装/コンパイラ内部の知識が必要になる • macroの実装を知っていないと名前解決に失敗したりする • 英数字でない名前をmangleしている、とか • シグネチャ •

    macroのための実装を別に用意する • 同じ仮引数名にしないといけない • エコシステム • ツールのサポートが弱い  http://docs.scala-lang.org/sips/pending/inline-meta.html
  74. inline化  class hello extends StaticAnnotation { inline def apply(defn:

    Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } }
  75. inlineキーワード • インライン化 • コンパイル時に定数化 • 関数呼び出しを削除 • ちゃんと型チェックしていて推論も可能 •

    `@inline`アノテーションは保証しない • ベストエフォート的なやつ • deprecatedになる予定(?) 
  76. metaキーワード  class hello extends StaticAnnotation { inline def apply(defn:

    Any): Any = meta { defn match { case d: Defn.Def => val newBody = q"""{ println("hello") ${d.body} }""" d.copy(body = newBody) case _ => abort("annotate only def") } } }
  77. アーキテクチャ • 分割されたAPI • stateless • syntactic • semantic •

    操作と必要な状態に応じて分割されている 
  78. Syntactic API • 構文に関するAPI • QuasiQuotesやpretty print • 例えばScalaのバージョンが状態となる 

    import scala.meta._ { import scala.meta.dialects.Scala211 "<hello />".parse[Term].get } { import scala.meta.dialects.Dotty "<hello />".parse[Term].get }
  79. Syntactic API • 構文に関するAPI • QuasiQuotesやpretty print • 例えばScalaのバージョンが状態となる 

    import scala.meta._ { import scala.meta.dialects.Scala211 "<hello />".parse[Term].get } { import scala.meta.dialects.Dotty "<hello />".parse[Term].get } ੒ޭ ྫ֎ൃੜ
  80.  object mirror { def show(str: String) = println(str) }

    object mirrorApp extends App { implicit val m = Mirror() m.sources.foreach { _.collect { case ref @ Term.Name("println") => println(s"symbol: ${ref.symbol}") }} }
  81.  object mirror { def show(str: String) = println(str) }

    object mirrorApp extends App { implicit val m = Mirror() m.sources.foreach { _.collect { case ref @ Term.Name("println") => println(s"symbol: ${ref.symbol}") }} }
  82.  object mirror { def show(str: String) = println(str) }

    object mirrorApp extends App { implicit val m = Mirror() m.sources.foreach { _.collect { case ref @ Term.Name("println") => println(s"symbol: ${ref.symbol}") }} } [info] symbol: _root_.scala.Predef.println(Ljava/lang/Object;)V. γϯϘϧ৘ใͷऔಘ
  83. その前に現状 • v1.8.0まではmacro annotationのみ • 色々とツールが作られている • http://scalameta.org/#BuiltwithScalameta • scalafmt:

    コードフォーマッタ • syntacticな操作 • scalafix: scalaの自動変換ツール(dotty化とか) • semanticな操作 
  84. 

  85. 

  86.