Vampire methods for structural types

I wish I could take credit for what I’m about to show you, because it’s easily the cleverest thing I’ve seen all week, but it’s Eugene Burmako’s trick and I’ve only simplified his demonstration a bit and adapted it to work in Scala 2.10.

First for the setup. Start your REPL like this to have it print the tree for every expression after the compiler’s cleanup phase:

scala -Xprint:cleanup

It’ll print some stuff you can ignore. Hit return to get a prompt (if you want), and then copy and paste the following:

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.Context
class body(tree: Any) extends StaticAnnotation
object Macros {
  def makeInstance = macro makeInstance_impl

  def makeInstance_impl(c: Context) = c.universe.reify[Any] {
    class Workaround {
      def z: Int = 13
      @body(42) def v: Int = macro Macros.selectField_impl
    new Workaround {}

  def selectField_impl(c: Context) = c.Expr(
      _.tpe <:< c.typeOf[body]

val myInstance = Macros.makeInstance

And it’ll print some more stuff you don’t need to worry about.

The makeInstance method here is pretty simple: we’re just defining a class and instantiating it (using the workaround I identified here). The inferred type of the instance will be a structural type with z and v methods.

And the punchline:

trait Foo {
  val zombie = myInstance.z
  val vampire = myInstance.v

Now you can start paying attention to all that stuff it’s been printing. Here’s the important part:

    def /*$read$$iw$$iw$Foo$class*/$init$($this: $line9.iw$Foo): Unit = {
        val qual1: Object = $line8.$read$$iw$$iw.myInstance();
        try {
  $line9.$read$$iw$$iw$Foo$class.reflMethod$Method1(qual1.getClass()).invoke(qual1, Array[Object]{})
} catch {
  case (1 @ (_: reflect.InvocationTargetException)) => throw 1.getCause()

This is what the trait’s constructor looks like after the cleanup phase. Notice all the ugly reflection business happening in the initialization of zombie—this is why you get warnings about reflective access when you use structural types in Scala, and why calling methods on structural types is (at least a little) slower.

Now look at the initialization for vampire. No reflection at all—the macro has just replaced myInstance.v with 42.

I missed this when Eugene first posted it on Twitter a couple of days ago—now I wish I could buy him a beer, because this totally made my Friday afternoon.