meta programming Scala

93bc8fb48f57c11e417dad9d26a2fb8a?s=47 petitviolet
September 09, 2017

meta programming Scala

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

93bc8fb48f57c11e417dad9d26a2fb8a?s=128

petitviolet

September 09, 2017
Tweet

Transcript

  1. 2.

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

    でScalaとかGo書いてます • https://www.facebook.com/fringeneer/ • http://fringeneer.hatenablog.com/ 
  2. 3.

    今日話すこと • メタプログラミングってなに? • Scalaで出来るメタプログラミング • scala.meta • 使い方イメージ •

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

    

  4. 13.

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

    Class.forName("Foo"); Method method = cl.getMethod("hello"); method.invoke(cl.newInstance()); } catch (Exception e) { e.printStackTrace(); }
  5. 14.

    よく使われるリフレクションもメタプログラミ ングの一種。 実行時/コンパイル時のオブジェクトにアクセス 出来る。  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. 15.

    よく使われるリフレクションもメタプログラミ ングの一種。 実行時/コンパイル時のオブジェクトにアクセス 出来る。  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. 16.

    

  8. 17.

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

    えば型情報が利用できる • implicit macro • implicit parameterの自動生成 • type provider • 型そのものを自動生成 • 構造的部分型 • 他にもいろいろ • マクロバンドルとか抽出子マクロとか  http://docs.scala-lang.org/ja/overviews/macros/usecases.html
  9. 18.

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

    えば型情報が利用できる • implicit macro • implicit parameterの自動生成 • type provider • 型そのものを自動生成 • 構造的部分型 • 他にもいろいろ • マクロバンドルとか抽出子マクロとか  http://docs.scala-lang.org/ja/overviews/macros/usecases.html
  10. 21.

    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. 22.

    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. 23.

    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. 24.

     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. 25.

     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. 26.

    

  16. 27.

    

  17. 29.

    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. 30.

    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. 31.

    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. 32.

    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. 33.

    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. 41.

    

  23. 43.

    

  24. 44.

    

  25. 45.

    

  26. 46.

    

  27. 47.

    

  28. 50.

     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. 51.

     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. 52.

     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. 53.

     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. 54.

     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. 55.

     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. 56.

     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. 57.

     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. 58.

     @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. 59.

     @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. 70.

     object TimeLoggingApp extends App { def heavy(n: Long): Long

    = { Thread.sleep(n) n } println(heavy(100)) }
  39. 71.

     object TimeLoggingApp extends App { @TimeLogging def heavy(n: Long):

    Long = { Thread.sleep(n) n } println(heavy(100)) }
  40. 72.

     [heavy]tracking time:101 ms 100 object TimeLoggingApp extends App {

    @TimeLogging def heavy(n: Long): Long = { Thread.sleep(n) n } println(heavy(100)) }
  41. 73.

     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. 74.

     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. 75.

     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. 78.

     @Length(min = 1, max = 10) val ok =

    1 to 10 @Length(min = 100) val ng = 1 to 10
  45. 79.

     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. 80.

     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. 81.

     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. 83.

     class MyClass(val n: Int, val s: String) object UnapplyApp

    extends App { val target = new MyClass(n = 100, s = "hoge") }
  49. 84.

     @Unapply class MyClass(val n: Int, val s: String) object

    UnapplyApp extends App { val target = new MyClass(n = 100, s = "hoge") }
  50. 85.

     @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. 86.

     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. 87.

     @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. 88.

     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. 89.

     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. 90.

     @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. 92.

     trait MyService { def double(i: Int): Int = i

    * 2 } object MyServiceApp extends App { }
  57. 93.

     trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  58. 94.

     trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  59. 95.

     trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  60. 96.

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

    i * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  61. 97.

     @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 cls@Defn.Class(_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) cls.copy(templ = template.copy(stats = Some(templateStats))) case obj@Defn.Object(_, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) obj.copy(templ = template.copy(stats = Some(templateStats))) case trt@Defn.Trait(_, _, _, _, 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. 98.

     @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 cls@Defn.Class(_, _, _, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) cls.copy(templ = template.copy(stats = Some(templateStats))) case obj@Defn.Object(_, _, template) => val templateStats: Seq[Stat] = addField +: template.stats.getOrElse(Nil) obj.copy(templ = template.copy(stats = Some(templateStats))) case trt@Defn.Trait(_, _, _, _, 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. 111.

    

  64. 116.

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

    i * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  65. 117.

     trait MyService { def double(i: Int): Int = i

    * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  66. 121.

     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. 122.

     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. 123.

    

  69. 125.

     @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. 126.

     @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. 127.

     @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. 128.

     @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. 129.
  74. 134.

    既存のマクロの問題点 • macro実装/コンパイラ内部の知識が必要になる • macroの実装を知っていないと名前解決に失敗したりする • 英数字でない名前をmangleしている、とか • シグネチャ •

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

    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") } } }
  76. 140.

    inlineキーワード • インライン化 • コンパイル時に定数化 • 関数呼び出しを削除 • ちゃんと型チェックしていて推論も可能 •

    `@inline`アノテーションは保証しない • ベストエフォート的なやつ • deprecatedになる予定(?) 
  77. 141.

    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") } } }
  78. 144.

    アーキテクチャ • 分割されたAPI • stateless • syntactic • semantic •

    操作と必要な状態に応じて分割されている 
  79. 148.

    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. 149.

    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 } ੒ޭ ྫ֎ൃੜ
  81. 152.

     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. 153.

     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}") }} }
  83. 154.

     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. γϯϘϧ৘ใͷऔಘ
  84. 157.

    その前に現状 • v1.8.0まではmacro annotationのみ • 色々とツールが作られている • http://scalameta.org/#BuiltwithScalameta • scalafmt:

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

    

  86. 162.

    

  87. 163.