In my previous blog on Scala 2.10 macros, I showed you how to write a macro that can swap the contents of two variables. In this blog, visiting scholar Martin Christensen and myself try to solve a problem from our blog on dynamic types in Scala 2.10.

In that blog, we had developed a DSL for writing XML, like this:

xml date {
   xml year "2006"
   xml month "01"
   xml day "01"
} // Yields <date><year>2006</date><month>01</month><day>01</day><date>

All those xml inside are a little unsightly, but you can't avoid them. A dynamic method must be called on a dynamic object.

Can one do better with macros?

A macro xml consumes some structure that contains XML elements, which themselves contain elements and text. What structure? That’s really up to the DSL designer. Since we used blocks in the dynamic type example, Martin chose to use blocks here as well.

Our first try was:

xml { 
  date {
    year "2006"
    month "01"
    day "01"
  }
}

Unfortunately, that's not legal Scala since date, year, and so on, are not defined. I suggested using symbols instead.

xml { 
  'date; {
    'year; "2006"
    'month; "01"
    'day; "01"
  }
}

The semicolons are needed because otherwise, it wouldn't be legal Scala.

An XML block can contain any sequence of

Exercise: To avoid those unsightly semicolons, define an implicit conversion from a symbol to an object that can consume a tuple sequences and a block.

Again, the mysterious $read.$iw.$iw.$iw.swap—the list of $iw gets longer as you keep working in the REPL. But you can clearly see what you get: two expressions of the form Ident(new TermName("...")).

That's enough information to write the swap_impl method:

def swap_impl(c: Context)(a: c.Expr[Any], b: c.Expr[Any]): c.Expr[Unit] = {
  import c.universe._
  import c.universe.Flag._
  val unitResult =  c.Expr[Unit](Literal(Constant(())))
  a.tree match { 
    case ia : Ident => b.tree match {
      case ib : Ident => c.Expr[Unit](Block( // Had to take out List
        ValDef(Modifiers(MUTABLE), newTermName("temp"), TypeTree(), ia),
        Assign(ia, ib), 
        Assign(ib, Ident(newTermName("temp")))))
      case _ => unitResult
    }
    case _ => unitResult // If you throw an exception, get error: exception during macro expansion:
  }
}

And it works:

object SwapTest extends App {
  import Swap._
  { // Need to define the variables in a block
    var a = 3 
    var b = 4 
    swap(a, b)
    println(a)
    println(b)
  }  
}

Exercise 1: What happens if you call swap(first, temp)? How can you fix it?

To my surprise, you get perfectly good error messages when you abuse the macro. For example, declare

var a = "Fred"

The error message says “type mismatch”. That makes sense. You can't swap a String and an Int.

Declare

val a = 3

and the error message says “reassignment to val”.

But not all is well yet. Try

var a = Array(3, 4)
swap(a(0), a(1))

Clearly, this can't work. Now the macro doesn't get an Ident(...). Recall that a(0) means a.apply(0). The macro gets an Apply(Select(..., newTermName("apply")), List(...)), as you can find out by using print/showRaw/reify.

Exercise 2: Do this. What happens? And what exactly is passed to the macro?

In this case, we want to call

int temp = a(0)
a(0) = a(1)
a(1) = temp

where the last two expressions are really

a.update(0, a.apply(1))
a.update(1, temp)

In other words, we are passed a tree containing a.apply(...), and we need to make a tree calling a.update(..., ...).

Finally, what if we do get fields of a class?

object SwapTest extends App {
  import Swap._
  var a = 3 
  var b = 4 
  swap(a, b)
  println(a)
  println(b)
}

Now, the call to swap receives the tree for the getter methods SwapTest.a and SwapTest.b.

Exercise 3: Verify this. What exactly is passed to the macro?

We want to generate calls to those getter methods for reading the value, and to the setter methods when writing:

int temp = SwapTest.a
SwapTest.a_$eq(SwapTest.b)
SwapTest.b_$eq(temp)

Here, a_$eq and b_$eq are the setter methods that are automatically generated for a var field. (If you defined them in Scala to replace the defaults, you'd call them a_= and b_=.)

To summarize, we'd like the swap macro to deal with three different kinds of arguments:

Here is the implementation of the macro that handles all three. The assign helper function deals with the three cases, turning them into an assignment, a call to update, or a call to the setter.

def swap_impl(c: Context)(a: c.Expr[Any], b: c.Expr[Any]): c.Expr[Unit] = {
  import c.universe._
  import c.universe.Flag._

  def assign(l: c.Expr[Any], r: c.Expr[Any]) = {
    l.tree match {
      case il : Ident => Assign(il, r.tree)
      case Apply(Select(obj, sel), List(index)) if sel.toString == "apply" => 
        Apply(Select(obj, newTermName("update")), List(index, r.tree))        
      case Select(obj, sel) => Apply(Select(obj, sel.toString + "_$eq"), List(r.tree))
      case _ => c.abort(l.tree.pos, "Expected variable or variable(index)")
    }
  }

  c.Expr[Unit](Block(
    ValDef(Modifiers(MUTABLE), newTermName("$temp"), TypeTree(), a.tree),
    assign(a, b),
    assign(b, c.Expr[Any](Ident(newTermName("$temp"))))))
}

Note how to report an error if none of the three cases occur. If you call swap(a + 1, b), then the tree won't match, and the abort method of the Context trait will cause an error report that points to the offending location.

Exercise 4: Call

val a = List(3, 4)
swap(a(0), b(0))

What error report do you get? Why?

Now you have seen a very basic macro, and you have seen that writing macros is no fun in Scala. But, to put it in perspective, it's a lot better than byte code engineering in Java.

But hope is on the way. The fellow who implemented all this, Eugene Burmako, is working on the macro paradise that should make common cases easier. Personally, I'll be happy when I can write

def swap_impl(c: Context)(a: c.Expr[Any], b: c.Expr[Any]) = {
  c.universe.reify { var temp = a; a = b; b = temp } 
}