Slide 1

Slide 1 text

メタプログラミング Scala Hiroki Komurasaki @petitviolet 

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text



Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text



Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text



Slide 27

Slide 27 text



Slide 28

Slide 28 text

scalameta 

Slide 29

Slide 29 text

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/ 

Slide 30

Slide 30 text

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/ 

Slide 31

Slide 31 text

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/ 

Slide 32

Slide 32 text

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/ 

Slide 33

Slide 33 text

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/ 

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

scalametaの 使い方 

Slide 39

Slide 39 text

scalametaの 使い方 (v1.8.0) 

Slide 40

Slide 40 text

どんな感じで使えるか 

Slide 41

Slide 41 text



Slide 42

Slide 42 text

 QuasiQuotes(ޙͰ

Slide 43

Slide 43 text



Slide 44

Slide 44 text



Slide 45

Slide 45 text



Slide 46

Slide 46 text



Slide 47

Slide 47 text



Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

@hello? 

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

 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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

 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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

 • IntelliJは展開してくれる Before

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

要するに 

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

ユースケース 

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

annotationでValidation 

Slide 77

Slide 77 text

 val ok = 1 to 10 val ng = 1 to 10

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

 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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

フィールドをinjection 

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

どうやるか 

Slide 100

Slide 100 text

QuasiQuotes 

Slide 101

Slide 101 text

だいたい QuasiQuotes でイケる 

Slide 102

Slide 102 text

• QuasiQuotes(準クォート) 

Slide 103

Slide 103 text

• QuasiQuotes(準クォート) 

Slide 104

Slide 104 text

• unapplyも可能 

Slide 105

Slide 105 text

• unapplyも可能 

Slide 106

Slide 106 text

• 変数として埋め込める 

Slide 107

Slide 107 text

• 変数として埋め込める 

Slide 108

Slide 108 text

• 変数として埋め込める 

Slide 109

Slide 109 text

• 頑張ってくれる 

Slide 110

Slide 110 text

• 頑張ってくれる 

Slide 111

Slide 111 text



Slide 112

Slide 112 text

• 優しい…? 

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text



Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

 @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") } … ܕύϥϝʔλɾίϯετϥΫλΛऔಘ

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

scalametaの 内部的な話 

Slide 131

Slide 131 text

そういえば 

Slide 132

Slide 132 text

scalameta 何がいいの? 

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

ダメらしい 

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

Stateless API • 状態に依存しないAPI • コンパイル時でもランタイムでも関係なく使 用可能なもの  object MetaApp extends App { import scala.meta._ println(Term.Name("x")) }

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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 }

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

今後の話 

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

new-style macro 

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text



Slide 162

Slide 162 text



Slide 163

Slide 163 text



Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

 @petitviolet