Scala

scala.meta

More than 1 year has passed since last update.


scala.meta



なぜ話題になっているのか


  • これまでScalaでマクロを書くにはscala-reflectを使っていた

  • ところがscala-reflectを使ったマクロはdeprecatedになるらしい

  • これからはscala.meta



scala.metaで何ができるか


  • Scalaのプログラムをいじることができる

def addStat(

classDefn: Defn.Class, stat: Stat): Defn.Class = {
val templ = classDefn.templ
val stats = templ.stats.getOrElse(Seq.empty[Stat])
val newStats = stats :+ stat
val newTempl = templ.copy(stats = Some(newStats))
classDefn.copy(templ = newTempl)
}



quasiquotes

scala> val tree = "x + y /* adds x and y */"

.parse[Term]
.get
tree: scala.meta.Term = x + y /* adds x and y */

scala> tree.syntax
res0: String = x + y /* adds x and y */

scala> tree.structure
res1: String = Term
.ApplyInfix(
Term.Name("x"),
Term.Name("+"),
Nil,
Seq(Term.Name("y")))



scala.metaでマクロを書く


  • 現時点ではmacro annotationがサポートされている

  • paradiseプラグインが必要



Hello world

import scala.meta._

class helloWorld extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
// compile時にhello worldされる
println("hello world from macro!!")
defn
}
}



ToString

@ToString

case class User(
id: Int,
name: String,
country: String,
createdAt: LocalDateTime
)



ToString

inline def apply(defn: Any): Any = meta {

...
defn match {
case Term.Block(Seq(classDefn: Defn.Class, objDefn: Defn.Object)) =>
Term.Block(Seq(addToStringMethod(classDefn), objDefn))
case classDefn: Defn.Class =>
val objName = Term.Name(classDefn.name.toString)
val objDefn = q"object $objName"
Term.Block(Seq(addToStringMethod(classDefn), objDefn))
case _ => sys.error("error")
}
}



ToString

def addToStringMethod(classDefn: Defn.Class): Stat = {

val className = classDefn.name.toString
val paramNames = classDefn.ctor.paramss
.flatten.map { param =>
val fieldName = param.name.toString
q"""${Lit(fieldName)} + "=" + ${Term.Name(fieldName)}"""
}
val body = paramNames
.reduce { (a, b) => q"""$a + ", " + $b""" }
val method =
q"""override def toString(): String =
${Lit(className)} + "(" + $body + ")""""

addStat(classDefn, method)
}



実用例


  • scalafmt



実用例

@compile("example/src/main/resources/application.conf")

object path

object Example {

def main(args: Array[String]): Unit = {
val config = ConfigFactory.load()
val serializer1 = config.getString(
path.akka.actor.serializers.`akka-containers`.full)
val serializer2 = config.getString(
"akka.actor.serializers.akka-containers")
assert(serializer1 == serializer2)
}

}



parameter


  • annotationにはパラメータを渡せる

  • literalのみ

val configFile = this match {

case q"new $_(${Lit(path)})" => new File(path.toString)
case _ => abort("config file is not specified")
}

val config = ConfigFactory
.parseFile(configFile)
.resolve()



コード生成

val configTree = Seq(

q"""
abstract class ConfigTree(val name: String,
val full: String)
"""

)

def defConfigTree(name: String, path: String, fullPath: String): Defn.Object = {
q"""object ${Term.Name(s"`$name`")}
extends ConfigTree(${Lit(path)}, ${Lit(fullPath)})"""

}



コード生成


  • 設定パスのツリーをたどっていく

def constructConfigTree[T: HasTemplate](

obj: T,
config: ConfigValue,
path: List[String]): T = {
config.valueType() match {
case ConfigValueType.OBJECT =>
val configObject =
config.asInstanceOf[ConfigObject]
val children = configObject.asScala.toMap
.map { case (key, value) =>
val child = defConfigTree(key, key, (path :+ key).mkString("."))
constructConfigTree(child, value, path :+ key)
}.to[Seq]
addStats(obj, Seq.empty[Stat], children)
case ConfigValueType.LIST |
ConfigValueType.NUMBER |
ConfigValueType.BOOLEAN |
ConfigValueType.STRING |
ConfigValueType.NULL =>
obj
}
}



生成されたコード

object path {

abstract class ConfigTree(val name: String, val full: String)
object `akka` extends ConfigTree("akka", "akka") {
object `actor` extends ConfigTree("actor", "akka.actor") {
object `serializers` extends ConfigTree("serializers", "akka.actor.serializers") {
object `akka
-containers` extends ConfigTree("akka-containers", "akka.actor.serializers.akka-containers")
}
}
}
}


  • application.confがでかいといっぱい生成されて気分が良い



Documents



所感


  • macro annotationだけだと物足りない


    • case classからSQLの生成などをやってみたがjoinなどまでやろうとすると表現力不足

    • annotationをつけたdefinitionの中しかいじれない


      • Lombokの @CleanUp が無理そう





  • def macro待ち

  • IntelliJの対応が不十分


    • macroで生成されたメソッドなどを参照すると赤くなる