Since macros are experimental, changes have come up between scala 2.10 and 2.11. This section will go over the differences.
In 2.10, the way you access Context
is by using import scala.reflect.macros.Context
. This gives you all the tree operations covered in the sections later, but introduces a function reify
which generates c.Expr
instances based off a block of code.
2.11 introduced big changes for how macros are built.
scala.reflect.macros.Context
has been split in twoscala.reflect.macros.blackbox.Context
scala.reflect.macros.whitebox.Context
reify
is not the only way to create treesq""" println("Hello World!") """
scala.reflect.macros.Context
still exists in 2.11, but its being replaced by two new Context
types found in blackbox and whitebox. If you look at the code, whitebox.Context
extends from blackbox.Context
, so what can whitebox do that blackbox cant? The main difference is that blackbox macros should act just like normal functions (A => B
) but whitebox macros can refine their return type. What this means is that whitebox macros can change their return type.
A example of when you would use whitebox over blackbox is a function that converts a case class into a tuple of the same size.
def toTuple[A <: Product](a: A): Any = macro CaseClassMacros.toTupleMacro[A]
In this example, calling it as toTuple(Foo("foo", "bar"))
yields (String, String)
as the return type. A good writeup on this can be found here.
Use whitebox macros with caution since IDE support is very limited (aka, intellij says that your code doesn't compile, but it does!). They can also be less clear to the caller since the types are no longer documentation. A common workaround for such a thing is by throwing an abstraction/typeclass over this function...
(I believe this was also in 2.10) Since functions can be macros, and implicits can be used with functions... see where I am going with this? Yep, implicit macro calls!
A common pattern of hiding that you are really using macros is by defining a typeclass that does what the user wants (CaseToTuple
), but the creation of the typeclass instance is via a macro.
implicit def caseToTuple[C,T]: CaseToTuple[C, T] = macro ...
This makes the user API clean since its defering the real whitebox stuff internally; it encapsulates the real macro.
With 2.11 (and macro paradise) comes a new way to build up trees; quasiquotes! Quasiquotes are really just string interpolation but generates c.universe.Tree
instances.
Simple example of quasiquotes:
q"""
case class MacroGenClass(name: String)
val mgc = MacroGenClass("some name!")
"""
This will generate a Block
that has the ClassDef
and ValDef
calls, and return Unit
.
Quasiquotes are your new best friend!