Beaucoup de code technique qui se répète dans votre base de code ? N'écrivez plus ce code, générez le, avec Scala meta.
Génération de méthodes avec des arités différentes
Reader basé sur un sealed trait + case objects...
Découvrez les deux types de macros possibles
A l'issue de cette présentation, vous saurez comment écrire votre première macro et comment l'intégrer à votre build.
5. Abstract Syntax Tree (AST)
Ce qui représente le programme.
On (d)écrit cet AST en utilisant une
arborescence de symbole
6. Tout est SYMBOLE en Scala
Tout ce que l’on peut définir ou donner un nom
En savoir + : symbols-trees-types
Symbole Mot clef ou entité
Class Classe
Term val, var, def, object...
Lit litéral (String, Int, etc…)
7. Symbole : Defn.Class
Le symbole porte toutes les informations de
l’entité à laquelle il fait référence
Defn.Class(
mods :Seq[scala.meta.Mod], // modifiers (Private, Implicit, final, sealed…)
name : Type.Name, // nom du type
tparams :Seq[Type.Param], // Types paramétrant la classe
ctor :Ctor.Primary, // contructeur
templ :Template// body de la class
)
8. Qu’est ce qu’une macro ?
AST
Code
Des symboles pour décrire un AST qui génère du
code
9. Annotation vs def macros
//def macros avec scala-reflect
implicit val userWrites = Json.writes[User]
//macro par annotations (scala-meta)
@ShowMacro(constructorParams)
case class Name(firstName: String, lastName: String)
10. Utiliser des macros est facile
case class User(firstName : String, lastName : String)
object User {
//def macro
implicit val userWrites = Json.writes[User]
//génère le code suivant
implicit val userWrites = (
(__ "firstName").write[String] and
(__ "lastName").write[String] )
(unlift(User.unapply))
}
11. Mais les écrire…
defn match {
case q"class $className[..$classTypes] { ..$body }" =>
val types = classTypes.map(t => Type.Name(t.name.value))
val typesName: Seq[Name] = classTypes.map(t => Term.Name(t.name.value))
val result =
q"""
class $className[..$classTypes] {
..$body
..${
typesName.zipWithIndex.map { case (currentTypeName, y) =>
val unionXType2UnionXMethodName = Term.Name("toUnion" + y)
val currentType = Type.Name(currentTypeName.value)
val unionXType = Term.Name("Union" + classTypes.size)
val constructorArgs : Seq[Term.Arg] = for (x <- typesName.indices) yield q"""${argByXY(x, y)}"""
q"implicit def $unionXType2UnionXMethodName(t : $currentType) = $unionXType[..$types](..$constructorArgs)"
}
}
14. Déconstruction de la class annotée
@ShowMacro //class annotée
case class Name(firstName: String, lastName: String)
---
class ShowMacro extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
defn match {
// 1) version simple
case Defn.Class(mods, name, tparams, ctor, template)=>
// 2) avec companion
case Seq(Defn.Class(m, n, t, c, t), companion: Defn.Object)=>
//3) version simplifiée avec quasiquotes
case q"case class $name(..$lFields)"=>
17. Code complet
package io.github.hamsters
import scala.collection.immutable.Seq
import scala.meta._
class ShowMacro extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
defn match {
case cls @ Defn.Class(_, _, _, ctor, _) =>
val show =q"""
implicit def showable = new io.github.hamsters.Showable[${cls.name}] { override def format(a: ${cls.name}) ={
import io.github.hamsters.ShowableSyntax._
${Lit.String(cls.name.value)}+ "(" + List(..${ctor.paramss.flatMap(_.map(pp => q"""${pp.name.syntax} + "=" +
Show.show(a.${Term.Name(pp.name.value)})""" ))}).reduce(_ + "," + _) + ")" } }"""
val companion = q"object ${Term.Name(cls.name.value)} { $show }"
val res = Term.Block(Seq(cls, companion))
//abort(res.syntax)
res
case _ => abort(defn.pos, "Invalid annottee - you can only use @Show on case classes")
}
}
}
18. Compilation, dans un module séparé
+ project
- macros
+ build.sbt
lazy val macros = project.settings(
libraryDependencies += "org.scalameta" %% "scalameta" %
"1.8.0" % Provided)
lazy val app = project.dependsOn(macros)
sbt clean && sbt compile
19. En savoir +
Hamster : Librairie Scala compatible avec les
débutants en programmation fonctionnelle (plein
de macros dedans)
AST Explorer : outil de visualisation
Refcard quasiquotes
Introduction pratique aux macros scala