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. メタプログラミング Scala Hiroki Komurasaki @petitviolet 

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

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

    ユースケース・実装例 • 実装方法 • scalametaの内部的な話 • ロードマップ 
  4. 目指すゴール • メタプログラミング怖くなくなる • コード読んでみようかなと思う • ちょっとやってみようかなって思う • scalametaやってみようかなって思う 

  5. メタプログラミングってなに? 

  6. メタ > メタ(meta-)とは、「高次な-」「超-」 「-間の」「-を含んだ」「-の後ろの」等の 意味の接頭語。ギリシア語から。 https://ja.wikipedia.org/wiki/メタ 

  7. メタプログラミングって何 > プログラミング技法の一種で、ロジックを直 接コーディングするのではなく、あるパターン をもったロジックを生成する高位ロジックに よってプログラミングを行う方法、またその高 位ロジックを定義する方法のこと。 https://ja.wikipedia.org/wiki/メタプログラミング 

  8. メタプログラミングって何 ざっくり言うと、プログラムを引数としてプロ グラムを出力とする関数みたいなもの。 ボイラープレートを削減できる! 

  9. 身近な例? • getter/setterの自動生成 • マクロで`log.debug()`を削除 • case class→classへの変換 • コンパイラがよしなにやってくれている

    • for式をflatMapとmapとwithFilterに展開 • desugar的な処理 
  10. Scalaで出来る メタプログラミング 

  11.  http://docs.scala-lang.org/ja/overviews/

  12. 

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

    Class.forName("Foo"); Method method = cl.getMethod("hello"); method.invoke(cl.newInstance()); } catch (Exception e) { e.printStackTrace(); }
  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
  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
  16. 

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

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

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

    A): String = macro DefMacroImpl.showImpl[A] }
  20. def macro • Scalaマクロの代表格(要出典  object DefMacro { def show[A](value:

    A): String = macro DefMacroImpl.showImpl[A] }
  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"))) } }
  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"))) } }
  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"))) } }
  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")
  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を使用
  26. 

  27. 

  28. scalameta 

  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/ 
  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/ 
  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/ 
  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/ 
  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/ 
  34. scalameta? • 現在Scalaのメタプログラミング関連で実装が 進んでいるモダンなライブラリ • Scalaのバージョンや実行環境を幅広くサポー トする 

  35. 今のうちに やっておくしかない 

  36. なにができる? • macro annotation • `@Foo class MyClass()` • Java-erには馴染みのあるやつ

    
  37. マクロアノテーション • ちなみに現行のScalaマクロでも可能 

  38. scalametaの 使い方 

  39. scalametaの 使い方 (v1.8.0) 

  40. どんな感じで使えるか 

  41. 

  42.  QuasiQuotes(ޙͰ

  43. 

  44. 

  45. 

  46. 

  47. 

  48.  hello world @hello def say() = { println("world") }

    say()
  49. @hello? 

  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") } } }
  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
  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Ͱ͢Αએݴ
  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ؔ਺
  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ͨ͠ର৅
  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ͷॻ͖׵͑
  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
  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͡Όͳ͚Ε͹ࣦഊ
  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") } } }
  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") } } }
  60.  • IntelliJは展開してくれる Before

  61.  • IntelliJは展開してくれる Before After

  62.  • IntelliJは展開してくれる Before After

  63. • (展開しなければ)ちゃんと動く  hello world @hello def say() = {

    println("world") } say()
  64.  • 別のBlockとして認識されているので動く

  65. 要するに 

  66. annotationした対象の ASTを書き換えられる 

  67. ユースケース 

  68. scalametaを使うと 例えば何が出来るのか 

  69. メソッドの実行時間を計測 

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

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

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

    @TimeLogging def heavy(n: Long): Long = { Thread.sleep(n) n } println(heavy(100)) }
  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!") } } }
  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!") } } } લޙͰ࣌ؒऔಘ
  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!") } } } ग़ྗ͍ͯ͠Δ͚ͩ
  76. annotationでValidation 

  77.  val ok = 1 to 10 val ng =

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

    1 to 10 @Length(min = 100) val ng = 1 to 10
  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
  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 """
  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ͳͷͰ࣮ߦ࣌ྫ֎
  82. コンパニオンオブジェクトを 自動で生成 

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

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

    UnapplyApp extends App { val target = new MyClass(n = 100, s = "hoge") }
  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!") } }
  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!") } }
  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") } }
  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.") } } }
  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ఆٛͷΈ
  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ͨ͠ΫϥεΛίϯύχΦϯΦϒδΣ ΫτΛ࣋ͭΫϥεͰஔ͖׵͑Δ
  91. フィールドをinjection 

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

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

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

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

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

    i * 2 } @MixIn[MyService](new MyService {}) object MyServiceApp extends App { println(myService.double(100)) }
  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" } }
  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ʹલड़ͷ஋Λ༩͑Δ
  99. どうやるか 

  100. QuasiQuotes 

  101. だいたい QuasiQuotes でイケる 

  102. • QuasiQuotes(準クォート) 

  103. • QuasiQuotes(準クォート) 

  104. • unapplyも可能 

  105. • unapplyも可能 

  106. • 変数として埋め込める 

  107. • 変数として埋め込める 

  108. • 変数として埋め込める 

  109. • 頑張ってくれる 

  110. • 頑張ってくれる 

  111. 

  112. • 優しい…? 

  113. • 超えちゃいけないライン 

  114. • QuasiQuotesは`q`だけじゃない • mod, param, t, tparam, etc.  https://github.com/scalameta/scalameta/blob/master/notes/quasiquotes.md

  115. コンストラクタ/型パラメータ 

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

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

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

    Any): Any = meta { println(impl) defn } }
  119.  class MixIn[T](impl: T) extends StaticAnnotation { inline def apply(defn:

    Any): Any = meta { println(impl) defn } }
  120. コンストラクタの値は 直接とれない 

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

  124.  Tͷܕ໊ ίϯετϥΫλ΁ͷҾ਺

  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") } …
  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`͸௚઀औΕͳ͍
  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") } … ܕύϥϝʔλɾίϯετϥΫλΛऔಘ
  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)͢Δͱྑ͍ ίϯύΠϧ࣌ͷϝοηʔδͱͯ͠දࣔ͞ΕΔ
  129. 辛いところ • QuasiQuotes補完効かない • 人間が頑張る • 知らないといけないことは多い • Tokenの種類とか一杯 •

    メタプログラムとしては正しいが、適用する と壊れるパターンに弱い 
  130. scalametaの 内部的な話 

  131. そういえば 

  132. scalameta 何がいいの? 

  133. 今のマクロじゃだめなの? 

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

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

  136. scalameta • 同じインタフェースでより良くする • ぱっと見はおまじない的な記述が減った程度 だが、内部のメカニズムは改善されている 

  137. scalameta • 同じインタフェースでより良くする • ぱっと見はおまじない的な記述が減った程度 だが、内部のメカニズムは改善されている • inline化 • metaキーワード

    
  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") } } }
  139. inlineキーワード • インライン化 • コンパイル時に定数化 • 関数呼び出しを削除 • ちゃんと型チェックしていて推論も可能 

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

    `@inline`アノテーションは保証しない • ベストエフォート的なやつ • deprecatedになる予定(?) 
  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") } } }
  142. metaキーワード • コンパイル時に実行するコードを決定 • スコープ内にscala.metaの機能を提供 • コンパイラが実行してASTを生成、置換 

  143. scalametaは どういう造りになっているか 

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

    操作と必要な状態に応じて分割されている 
  145. Stateless API • 状態に依存しないAPI • コンパイル時でもランタイムでも関係なく使 用可能なもの 

  146. Stateless API • 状態に依存しないAPI • コンパイル時でもランタイムでも関係なく使 用可能なもの  object MetaApp

    extends App { import scala.meta._ println(Term.Name("x")) }
  147. Syntactic API • 構文に関するAPI • QuasiQuotesやpretty print • 例えばScalaのバージョンが状態となる 

  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 }
  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 } ੒ޭ ྫ֎ൃੜ
  150. Semantic API • 意味情報に関するAPI • 名前解決、型チェック、メンバの列挙など • `Mirror`を状態として受け取る • プログラム内で使用可能な定義/依存関係の

    indexにあたるものをカプセル化したtrait • “semantic”な操作に必要となる 
  151.  object mirror { def show(str: String) = println(str) }

  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}") }} }
  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}") }} }
  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. γϯϘϧ৘ใͷऔಘ
  155. 3種類のAPI • stateless • いつでも使える • syntactic • 例えばコードフォーマッタ •

    semantic • 例えばlinter 
  156. 今後の話 

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

    コードフォーマッタ • syntacticな操作 • scalafix: scalaの自動変換ツール(dotty化とか) • semanticな操作 
  158.  http://docs.scala-lang.org/ja/overviews/

  159. new-style macro 

  160. new-style macro  v1.8.0時点までの話

  161. 

  162. 

  163. 

  164.  scalaマクロの後釜を 狙っていたが方向性を変えた

  165. scalamacros/scalamacros • scalametaとは別プロジェクトとして進行 • scalametaをベースとしている • new-style macro • 絶賛実装中/Dotty対応中

    
  166. サンプルコード置き場 • petitviolet/scalameta-prac • petitviolet/scala-logging • petitviolet/scala-acase 

  167. 目指すゴール • メタプログラミング怖くなくなる • コード読んでみようかなと思う • ちょっとやってみようかなって思う • scalametaやってみようかなって思う 

  168. 今後に期待しつつ scalametaを予習! 

  169.  @petitviolet