Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

    View Slide

  2. 自己紹介
    • 小紫 弘貴(Hiroki Komurasaki)
    • インターネットだと@petitviolet
    • Scala歴2年くらい
    • でScalaとかGo書いてます
    • https://www.facebook.com/fringeneer/
    • http://fringeneer.hatenablog.com/

    View Slide

  3. 今日話すこと
    • メタプログラミングってなに?
    • Scalaで出来るメタプログラミング
    • scala.meta
    • 使い方イメージ
    • ユースケース・実装例
    • 実装方法
    • scalametaの内部的な話
    • ロードマップ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Scalaで出来る
    メタプログラミング

    View Slide


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

    View Slide


  12. View Slide

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

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

    View Slide

  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

    View Slide

  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

    View Slide


  16. View Slide

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

    http://docs.scala-lang.org/ja/overviews/macros/usecases.html

    View Slide

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

    http://docs.scala-lang.org/ja/overviews/macros/usecases.html

    View Slide

  19. def macro
    • Scalaマクロの代表格(要出典

    object DefMacro {
    def show[A](value: A): String = macro DefMacroImpl.showImpl[A]
    }

    View Slide

  20. def macro
    • Scalaマクロの代表格(要出典

    object DefMacro {
    def show[A](value: A): String = macro DefMacroImpl.showImpl[A]
    }

    View Slide

  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")))
    }
    }

    View Slide

  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")))
    }
    }

    View Slide

  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")))
    }
    }

    View Slide


  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")

    View Slide


  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を使用

    View Slide


  26. View Slide


  27. View Slide

  28. scalameta

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. scalametaの
    使い方

    View Slide

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

    View Slide

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

    View Slide


  41. View Slide


  42. QuasiQuotes(ޙͰ

    View Slide


  43. View Slide


  44. View Slide


  45. View Slide


  46. View Slide


  47. View Slide


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

    View Slide

  49. @hello?

    View Slide


  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")
    }
    }
    }

    View Slide


  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

    View Slide


  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Ͱ͢Αએݴ

    View Slide


  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ؔ਺

    View Slide


  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ͨ͠ର৅

    View Slide


  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ͷॻ͖׵͑

    View Slide


  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

    View Slide


  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͡Όͳ͚Ε͹ࣦഊ

    View Slide


  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")
    }
    }
    }

    View Slide


  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")
    }
    }
    }

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide

  63. • (展開しなければ)ちゃんと動く

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

    View Slide


  64. • 別のBlockとして認識されているので動く

    View Slide

  65. 要するに

    View Slide

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

    View Slide

  67. ユースケース

    View Slide

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

    View Slide

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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


  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!")
    }
    }
    }

    View Slide


  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!")
    }
    }
    }
    લޙͰ࣌ؒऔಘ

    View Slide


  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!")
    }
    }
    }
    ग़ྗ͍ͯ͠Δ͚ͩ

    View Slide

  76. annotationでValidation

    View Slide


  77. val ok = 1 to 10
    val ng = 1 to 10

    View Slide


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

    View Slide


  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

    View Slide


  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
    """

    View Slide


  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ͳͷͰ࣮ߦ࣌ྫ֎

    View Slide

  82. コンパニオンオブジェクトを
    自動で生成

    View Slide


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

    View Slide


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

    View Slide


  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!")
    }
    }

    View Slide


  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!")
    }
    }

    View Slide


  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") }
    }

    View Slide


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

    View Slide


  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ఆٛͷΈ

    View Slide


  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ͨ͠ΫϥεΛίϯύχΦϯΦϒδΣ
    ΫτΛ࣋ͭΫϥεͰஔ͖׵͑Δ

    View Slide

  91. フィールドをinjection

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


  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 [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"
    }
    }

    View Slide


  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 [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ʹલड़ͷ஋Λ༩͑Δ

    View Slide

  99. どうやるか

    View Slide

  100. QuasiQuotes

    View Slide

  101. だいたい
    QuasiQuotes
    でイケる

    View Slide

  102. • QuasiQuotes(準クォート)

    View Slide

  103. • QuasiQuotes(準クォート)

    View Slide

  104. • unapplyも可能

    View Slide

  105. • unapplyも可能

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  109. • 頑張ってくれる

    View Slide

  110. • 頑張ってくれる

    View Slide


  111. View Slide

  112. • 優しい…?

    View Slide

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

    View Slide

  114. • QuasiQuotesは`q`だけじゃない
    • mod, param, t, tparam, etc.

    https://github.com/scalameta/scalameta/blob/master/notes/quasiquotes.md

    View Slide

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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide

  120. コンストラクタの値は
    直接とれない

    View Slide


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

    View Slide


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

    View Slide


  123. View Slide


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

    View Slide


  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")
    }

    View Slide


  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`͸௚઀औΕͳ͍

    View Slide


  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")
    }

    ܕύϥϝʔλɾίϯετϥΫλΛऔಘ

    View Slide


  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)͢Δͱྑ͍
    ίϯύΠϧ࣌ͷϝοηʔδͱͯ͠දࣔ͞ΕΔ

    View Slide

  129. 辛いところ
    • QuasiQuotes補完効かない
    • 人間が頑張る
    • 知らないといけないことは多い
    • Tokenの種類とか一杯
    • メタプログラムとしては正しいが、適用する
    と壊れるパターンに弱い

    View Slide

  130. scalametaの
    内部的な話

    View Slide

  131. そういえば

    View Slide

  132. scalameta
    何がいいの?

    View Slide

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

    View Slide

  134. 既存のマクロの問題点
    • macro実装/コンパイラ内部の知識が必要になる
    • macroの実装を知っていないと名前解決に失敗したりする
    • 英数字でない名前をmangleしている、とか
    • シグネチャ
    • macroのための実装を別に用意する
    • 同じ仮引数名にしないといけない
    • エコシステム
    • ツールのサポートが弱い

    http://docs.scala-lang.org/sips/pending/inline-meta.html

    View Slide

  135. ダメらしい

    View Slide

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

    View Slide

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

    View Slide

  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")
    }
    }
    }

    View Slide

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

    View Slide

  140. inlineキーワード
    • インライン化
    • コンパイル時に定数化
    • 関数呼び出しを削除
    • ちゃんと型チェックしていて推論も可能
    • `@inline`アノテーションは保証しない
    • ベストエフォート的なやつ
    • deprecatedになる予定(?)

    View Slide

  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")
    }
    }
    }

    View Slide

  142. metaキーワード
    • コンパイル時に実行するコードを決定
    • スコープ内にscala.metaの機能を提供
    • コンパイラが実行してASTを生成、置換

    View Slide

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

    View Slide

  144. アーキテクチャ
    • 分割されたAPI
    • stateless
    • syntactic
    • semantic
    • 操作と必要な状態に応じて分割されている

    View Slide

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

    View Slide

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

    object MetaApp extends App {
    import scala.meta._
    println(Term.Name("x"))
    }

    View Slide

  147. Syntactic API
    • 構文に関するAPI
    • QuasiQuotesやpretty print
    • 例えばScalaのバージョンが状態となる

    View Slide

  148. Syntactic API
    • 構文に関するAPI
    • QuasiQuotesやpretty print
    • 例えばScalaのバージョンが状態となる

    import scala.meta._
    {
    import scala.meta.dialects.Scala211
    "".parse[Term].get
    }
    {
    import scala.meta.dialects.Dotty
    "".parse[Term].get
    }

    View Slide

  149. Syntactic API
    • 構文に関するAPI
    • QuasiQuotesやpretty print
    • 例えばScalaのバージョンが状態となる

    import scala.meta._
    {
    import scala.meta.dialects.Scala211
    "".parse[Term].get
    }
    {
    import scala.meta.dialects.Dotty
    "".parse[Term].get
    }
    ੒ޭ
    ྫ֎ൃੜ

    View Slide

  150. Semantic API
    • 意味情報に関するAPI
    • 名前解決、型チェック、メンバの列挙など
    • `Mirror`を状態として受け取る
    • プログラム内で使用可能な定義/依存関係の
    indexにあたるものをカプセル化したtrait
    • “semantic”な操作に必要となる

    View Slide


  151. object mirror {
    def show(str: String) = println(str)
    }

    View Slide


  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}")
    }}
    }

    View Slide


  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}")
    }}
    }

    View Slide


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

    View Slide

  155. 3種類のAPI
    • stateless
    • いつでも使える
    • syntactic
    • 例えばコードフォーマッタ
    • semantic
    • 例えばlinter

    View Slide

  156. 今後の話

    View Slide

  157. その前に現状
    • v1.8.0まではmacro annotationのみ
    • 色々とツールが作られている
    • http://scalameta.org/#BuiltwithScalameta
    • scalafmt: コードフォーマッタ
    • syntacticな操作
    • scalafix: scalaの自動変換ツール(dotty化とか)
    • semanticな操作

    View Slide


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

    View Slide

  159. new-style macro

    View Slide

  160. new-style macro

    v1.8.0時点までの話

    View Slide


  161. View Slide


  162. View Slide


  163. View Slide


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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  169. @petitviolet

    View Slide