|
| 1 | +--- |
| 2 | +layout: overview-large |
| 3 | +title: Extractor Macros |
| 4 | + |
| 5 | +disqus: true |
| 6 | + |
| 7 | +partof: macros |
| 8 | +num: 6 |
| 9 | +--- |
| 10 | +<span class="label warning" style="float: right;">EXPERIMENTAL</span> |
| 11 | + |
| 12 | +**Eugene Burmako** |
| 13 | + |
| 14 | +Extractor macros are shipped with the recent milestone builds of Scala 2.11, starting from 2.11.0-M5, enabled by name-based extractors introduced by Paul Phillips in Scala 2.11.0-M5. Extractor macros are not available in Scala 2.10.x or in macro paradise. Follow the instructions at [http://www.scala-lang.org/download/](http://www.scala-lang.org/download/) to download and use the latest milestone of 2.11. |
| 15 | + |
| 16 | +### The pattern |
| 17 | + |
| 18 | +In a nutshell, given an unapply method (for simplicity, in this |
| 19 | +example the scrutinee is of a concrete type, but it's also possible |
| 20 | +to have the extractor be polymorphic, as demonstrated in the tests): |
| 21 | + |
| 22 | + def unapply(x: SomeType) = ??? |
| 23 | + |
| 24 | +One can write a macro that generates extraction signatures for unapply |
| 25 | +on per-call basis, using the target of the calls (`c.prefix`) and the type |
| 26 | +of the scrutinee (that comes with `x`), and then communicate these signatures |
| 27 | +to the typechecker. |
| 28 | + |
| 29 | +For example, here's how one can define a macro that simply passes |
| 30 | +the scrutinee back to the pattern match (for information on how to |
| 31 | +express signatures that involve multiple extractees, visit |
| 32 | +[scala/scala#2848](https://github.com/scala/scala/pull/2848)). |
| 33 | + |
| 34 | + def unapply(x: SomeType) = macro impl |
| 35 | + def impl(c: Context)(x: c.Tree) = { |
| 36 | + q""" |
| 37 | + new { |
| 38 | + class Match(x: SomeType) { |
| 39 | + def isEmpty = false |
| 40 | + def get = x |
| 41 | + } |
| 42 | + def unapply(x: SomeType) = new Match(x) |
| 43 | + }.unapply($x) |
| 44 | + """ |
| 45 | + } |
| 46 | + |
| 47 | +In addition to the matcher, which implements domain-specific |
| 48 | +matching logic, there's quite a bit of boilerplate here, but |
| 49 | +every part of it looks necessary to arrange a non-frustrating dialogue |
| 50 | +with the typer. Maybe something better can be done in this department, |
| 51 | +but I can't see how, without introducing modifications to the typechecker. |
| 52 | + |
| 53 | +Even though the pattern uses structural types, somehow no reflective calls |
| 54 | +are being generated (as verified by `-Xlog-reflective-calls` and then |
| 55 | +by manual examination of the produced code). That's a mystery to me, but |
| 56 | +that's also good news, since that means that extractor macros aren't |
| 57 | +going to induce performance penalties. |
| 58 | + |
| 59 | +Almost. Unfortunately, I couldn't turn matchers into value classes |
| 60 | +because one can't declare value classes local. Nevertheless, |
| 61 | +I'm leaving a canary in place ([neg/t5903e](https://github.com/scala/scala/blob/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/neg/t5903e/Macros_1.scala#L1)) that will let us know |
| 62 | +once this restriction is lifted. |
| 63 | + |
| 64 | +### Use cases |
| 65 | + |
| 66 | +In particular, the pattern can be used to implement shapeshifting |
| 67 | +pattern matchers for string interpolators without resorting to dirty |
| 68 | +tricks. For example, quasiquote unapplications can be unhardcoded now: |
| 69 | + |
| 70 | + def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { |
| 71 | + ... |
| 72 | + fun.tpe match { |
| 73 | + case ExtractorType(unapply) if mode.inPatternMode => |
| 74 | + // this hardcode in Typers.scala is no longer necessary |
| 75 | + if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) |
| 76 | + else doTypedUnapply(tree, fun0, fun, args, mode, pt) |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | +Rough implementation strategy here would involve writing an extractor |
| 81 | +macro that destructures `c.prefix`, analyzes parts of `StringContext` and |
| 82 | +then generates an appropriate matcher as outlined above. |
| 83 | + |
| 84 | +Follow our test cases at [run/t5903a](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903a), |
| 85 | +[run/t5903b](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903b), |
| 86 | +[run/t5903c](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903c), |
| 87 | +[run/t5903d](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903d) to see implementations |
| 88 | +of this and other use cases for extractor macros. |
| 89 | + |
| 90 | +Please note that extractor macros must be [whitebox](/overviews/macros/blackbox-whitebox.html), otherwise they will not work. |
0 commit comments