This doc page is specific to features shipped in Scala 2, which have either been removed in Scala 3 or replaced by an alternative. Unless otherwise stated, all the code examples in this page assume you are using Scala 2.
EXPERIMENTAL
Eugene Burmako
Extractor macros are a feature of Scala 2.11.x and Scala 2.12.x, enabled by name-based extractors introduced by Paul Phillips in Scala 2.11.0-M5. Extractor macros are not supported in Scala 2.10.x. They are also not supported in macro paradise for Scala 2.10.x.
The pattern
In a nutshell, given an unapply method (for simplicity, in this example the scrutinee is of a concrete type, but it’s also possible to have the extractor be polymorphic, as demonstrated in the tests):
def unapply(x: SomeType) = ???
One can write a macro that generates extraction signatures for unapply
on per-call basis, using the target of the calls (c.prefix
) and the type
of the scrutinee (that comes with x
), and then communicate these signatures
to the typechecker.
For example, here’s how one can define a macro that simply passes the scrutinee back to the pattern match (for information on how to express signatures that involve multiple extractees, visit scala/scala#2848).
def unapply(x: SomeType) = macro impl
def impl(c: Context)(x: c.Tree) = {
q"""
new {
class Match(x: SomeType) {
def isEmpty = false
def get = x
}
def unapply(x: SomeType) = new Match(x)
}.unapply($x)
"""
}
In addition to the matcher, which implements domain-specific matching logic, there’s quite a bit of boilerplate here, but every part of it looks necessary to arrange a non-frustrating dialogue with the typer. Maybe something better can be done in this department, but I can’t see how, without introducing modifications to the typechecker.
Even though the pattern uses structural types, somehow no reflective calls
are being generated (as verified by -Xlog-reflective-calls
and then
by manual examination of the produced code). That’s a mystery to me, but
that’s also good news, since that means that extractor macros aren’t
going to induce performance penalties.
Almost. Unfortunately, I couldn’t turn matchers into value classes because one can’t declare value classes local. Nevertheless, I’m leaving a canary in place (neg/t5903e) that will let us know once this restriction is lifted.
Use cases
In particular, the pattern can be used to implement shapeshifting pattern matchers for string interpolators without resorting to dirty tricks. For example, quasiquote unapplications can be unhardcoded now:
def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = {
...
fun.tpe match {
case ExtractorType(unapply) if mode.inPatternMode =>
// this hardcode in Typers.scala is no longer necessary
if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...)
else doTypedUnapply(tree, fun0, fun, args, mode, pt)
}
}
Rough implementation strategy here would involve writing an extractor
macro that destructures c.prefix
, analyzes parts of StringContext
and
then generates an appropriate matcher as outlined above.
Follow our test cases at run/t5903a, run/t5903b, run/t5903c, run/t5903d to see implementations of this and other use cases for extractor macros.
Blackbox vs whitebox
Extractor macros must be whitebox. If you declare an extractor macro as blackbox, it will not work.