code generation with scala.meta

Germany, New Zealand, California


@pollmeier

objective for this talk

  • understand: basics of scala.meta and the project status
  • believe: easier to write/debug than old macros
  • encourage: use and write new style macros

old style macros will be phased out

  • hard to write and debug, many rough edges
  • scala.meta macros will replace them
  • works with 2.11, 2.12, dotty, scala.js, scala-native

metaprogramming

  • code that manipulates code
  • verify code: against a schema (db, endoint, style)
  • amend code: performance metrics, logging
  • generate code
    • domain model to case classes
    • serialisation macro
    • ...

scala.meta

  • metaprogramming library
  • executed at compile time using a compiler plugin
  • DSL to parse/transform/generate code
  • macro wired up with annotation on any block of code

feature: code parsing

  • complete AST, including comments
  • lots of material online
  • scalafmt, scalafix

feature: code generation

e.g. generic serialiser

              case class Customer(name: String, yearOfBirth: Int)
            

              object Customer {
                def toMap(c: Customer): Map[String, Any] = ???
              }
            

wiring: macro with inline/meta


            @mappable case class Customer(name: String, yearOfBirth: Int)
            

            class mappable extends StaticAnnotation {
              inline def apply(defn: Any): Any = meta {
                defn
              }
            }
            

scala.meta ADT


            // Definition of object, class, type etc.
            trait Defn.Object(mods: Seq[Mod], name: Term.Name,
              templ: Template)

            trait Defn.Class(mods: Seq[Mod], name: Type.Name,
              tparams: Seq[Type.Param], ctor: Ctor.Primary,
              templ: Template)

            trait Defn.Type(mods: Seq[Mod], name: Type.Name,
              tparams: Seq[Type.Param], body: Type)
            

            // Different namespaces for names
            trait Type.Name(value: String) // type name, e.g. List.type
            trait Term.Name(value: String) // expression, e.g. List companion object
            

quasiquotes

  • quasiquotes came from scala.reflect
  • DSL to construct/deconstruct scala.meta ADT
  • reference documentation at notes/quasiquotes.md

construct and deconstruct ADT using quasiquotes


              val clazz = q"case class Foo(i: Int)" //construction
            

              clazz match {  // deconstruction
                case q"""..$mods class $tname[..$tparams]
                         ..$ctorMods (...$paramss) extends $template""" ⇒  
              }
            
  • for construction, quasiquotes are always easier
  • for deconstruction start with ADT

nesting quasiquotes


              q"""object MyObject {
                $clazz          //Defn.Class
                ..$otherClasses //Seq[Defn.Class]
              }"""
            

special quasiquote interpolators

to resolve ambiguities

              q"List"  //Term.Name: List companion object
              t"List"  //Type.Name: List type
            

live coding

  • understand code pipeline
  • make the same steps a new user would take
  • build generic serialiser: class ⇒ Map[String, Any]

          import scala.meta.serialiser._
              
          object Foo { def apple = "bar"}

          @mappable case class Foo[A](
            i: Option[Int] = Some(42),
            @mappedTo("aMapped") a: A,
            @nullable s: String) {
            def banana = i.get
          }

          Foo.toMap(Foo(a = true, s = "bar"))
          // Map(i -> 42, aMapped -> true, s -> bar)
          Foo.fromMap(Map("i" -> 42, "aMapped" -> true, "s" -> "bar"))
          Foo.fromMap(Map("aMapped" -> true))
          
github.com/mpollmeier/scalameta-serialiser

advanced FAQ

passing values to annotation


            @myMacro("fooValue") class MyClass
            

            class myMacro(foo: String) extends StaticAnnotation {
              inline def apply(defn: Any): Any = meta {
                println(foo) // error: value not found

                this match {
                  case q"new $_()" ⇒ // no params defined
                  case q"new $_(params))" ⇒ 
                }
              }
            }
            
annotating class members only possible with non-meta annotations

            @mappable case class CC(@nullable s: String)
            

            class nullable extends StaticAnnotation
            // no inline/meta block allowed, but likely not necessary
            

Discussion at scalameta/paradise #16 and #192

Smallprint (*)

REPL expansion of macros doesn't work yet

              sbt console
              [error] error while loading Object,
              [error] Missing dependency 'object scala in compiler mirror'
            

Workaround: factor out generated code into separate project
Discussion at scalameta/paradise #10

Intellij

  • scala plugin: change to nightly
  • code completion works, allows to expand macro
  • some help for writing macro, incl. quasiquotes

Ensime

  • code completion of expanded macro works
  • inside meta block: currently no help (used to work)

final thoughts

  • great metaprogramming DSL
  • support for all scala platforms
  • some rough edges
  • IDE support needs to mature
  • active & responsive community
  • scaladoc should be hosted online
  • scala.meta needs a logo
www.michaelpollmeier.com
github.com/mpollmeier
twitter.com/pollmeier
Michael Pollmeier ~ code generation with scala.meta