Travis Brown @travisbrown
Getting started with macros now that we're on 2.10 cc @jco pic.twitter.com/QXcyGrjWCU
— (ノ`□´)ノ⌒ʇləʌəıN əoſ (@joenievelt) September 5, 2014
As a child, I wondered why wizards didn't just use magic to solve all their problems. 25 years later, I have Scala macros and I understand.
— Jon Pretty (@propensive) November 19, 2013
The Scala language is already too complex
The Scala compiler is already too slow
You'll have to rewrite for 2.11 and 2.12
They don't work with Pants*
*Because of the need for separate compilation; you can define macros and publish them, but can't use macros you've defined without publishing.
You learn a lot about the guts of Scala writing macros
Programs that write programs are fun
In some cases they're worth the danger
Macros are driving language evolution
My personal problem
I've been using since (almost) the beginning
I've contributed macro code to dozens of projects
I'm trying really hard not to oversell here
Rules for rewriting code
#define RADTODEG(x) ((x) * 57.29578)
Introduced in 2.10 by Eugene Burmako
New syntax and one new keyword
Some features require plugin in 2.10
Reducing boilerplate
Improving performance
More compile-time safety
Smarter test reporting
Extending the language
case class User(id: Long, name: String, email: String)
body.as[User] // Read JSON representation of user
body.as[Long => User] // Read id-less JSON user
body.as[User => User] // Read partial JSON user
val dct = PrefixGenerator.fromSchema[Rdf]("/dctype.rdf")
val dc = PrefixGenerator.fromSchema[Rdf]("/dcterms.rdf")
val frankensteinNotebookB = url.a(dct.Text)
-- dc.title ->- "Frankenstein Draft Notebook B"
-- dc.creator ->- URI(
"https://en.wikipedia.org/wiki/Mary_Shelley"
)
for {
author <- getAuthor(postId)
tags <- getTags(postId)
bio <- getAuthorInfo(author)
page <- createPage(author, tags, bio)
} yield page
getAuthor(postId).flatMap { author =>
getTags(postId).flatMap { tags =>
getAuthorInfo(author).flatMap { bio =>
createPage(author, tags, bio)
}
}
}
getAuthor(postId).join(getTags(postId)).flatMap {
case (author, tags) =>
getAuthorInfo(author).flatMap { bio =>
createPage(author, tags, bio)
}
}
}
import spire.syntax.cfor._
cfor(0)(_ < 10, _ + 1) { i =>
println(i)
}
// works fine
def foo(name: String, id: Int) = f"$name: $id%08d"
// doesn't compile
def bar(name: String, id: Int) = f"$id: $name%08d"
scala> assert("hello".startsWith("h") && "goodbye".endsWith("y"))
org.scalatest.exceptions.TestFailedException:
assert("hello".startsWith("h") && "goodbye".endsWith("y"))
| | | | | | |
"hello" true "h" | "goodbye" false "y"
false
import shapeless.test.illTyped
illTyped("""
class Foo(i: Int)
val foo = new Foo("a")
""")
import language.experimental.macros, reflect.macros.Context
def tupleFillImpl[A: c.WeakTypeTag](c: Context)
(n: c.Expr[Int])(a: c.Expr[A]) = {
import c.universe._
val howMany = n.tree match {
case Literal(Constant(i: Int)) => i
case _ => c.abort(
c.enclosingPosition,
"Must provide a literal integer!"
)
}
c.Expr[Any](q"""(..${List.fill(howMany)(a)})""")
}
No new syntax in this part: all library-level
You can run this example in a 2.10 or 2.11 REPL
scala> def tupleFill[A](n: Int)(a: A) = macro tupleFillImpl[A]
defined term macro tupleFill: [A](n: Int)(a: A)Any
scala> tupleFill(4)("hey")
res0: (String, String, String, String) = (hey,hey,hey,hey)
One new keyword in the macro method
Note the statically inferred return type
(Also note the failed syntax highlighting)
Increasing degrees of danger
enclosing*
The macro reflection API is changing rapidly
Quasiquotes are intended to be more stable
Whitebox: can't provide a type signature before expansion
Blackbox: type signature is required (and conformed to)
Extremely powerful: can introduce new top-level classes
Much more experimental than def macros
Extremely powerful: look at code that's not an argument
Already deprecated in 2.11
blackbox def macros + quasiquotes + no enclosing*
import com.twitter.algebird._
import com.twitter.algebird.macros.caseclass._
import com.twitter.algebird.macros.ArbitraryCaseClassMacro._
case class Foo(s: String, i: Int, d: Double)
implicitly[Monoid[Foo]].zero
arbitrary[Foo].arbitrary.sample