Konrad is passionate about the JVM, and the whole ecosystem that surrounds it. Lately he fell in love with Scala, but that doesn't mean he's not into Java or dynamic languages. Other than that, he?s a fan of automating every possible task and ridiculously long keyboard shortcuts. "After hours" he's still bound to programming - as a lead of the PolishJUG, the lead of the Google Developers Group Kraków and helping hand of Software Craftsmanship Kraków he's coding for fun and glory, speaking at conferences, or organising meetups ranging from small hackathons to big conferences like the annual GeeCON. In those rare times when he's not doing something code-related, he's collecting game consoles or playing tennis / squash. He bloggs and tweets. Konrad is a DZone MVB and is not an employee of DZone and has posted 12 posts at DZone. You can read more from them at their website. View Full User Profile

Scala 2.10 – Macros Hands-on With "Method Alias"

02.20.2013
| 3957 views |
  • submit to reddit

Where?

Last week I was presenting at JFokus (cool conference, give it a try), a really nice JVM-focused conference. Obviously I love the conference I co-organise with fellow JUG members Geecon, too but it’s always fantastic to be a participant/speaker rather than the organiser once in a while – then you actually can enjoy the talks and not run around arranging everything… :-)

After the last day of the conference there was a Stockholm Scala User group meetup which I’ve obviously had to go to :-) With a beer in hand we jokingly invented a @alias annotation that would allow you to setup aliases for symbolic methods. An example would be akka’s (scala api) that delegates to tell (java api). While I had nothing to do waiting for the plane to arrive at the airport, I hacked up a proof of concept macro that does this.

As a heads up, all the code is on github: https://github.com/ktoso/scala-macro-method-alias

What?

I came up with two syntaxes, the first one being more scala-like:

And another one, wich looks more java-like (due to the annotation ;-)), and we’ll in the joke we said it should be an annotation:

Also it has been a great occasion to try out how implementing macros actually feels – and I gotta say, we’re not in lisp heaven here, but it’s pretty ok once you get a feel for it. Static typing for the win I’d say. Also, in 2.11 we’ll probably get a “quasiquoting string interpolator”, which would make creating Trees even easier.

In this post we’ll look at how implementing a macro in Scala 2.10 looks like. We’ll just cover the 1st syntax mentioned here (1st image).

How?

Let’s start by looking at the code then, shall we? On the call site we don’t have much to do. We just import the macro, and call it with the method (partially applied). The macro will then look at this method’s signature and delegate all params to it.

import pl.project13.scala.macros.Alias.aliasFor
 
def !!(a: String, b: Int) = aliasFor { manyParams _ }

Very well… Let’s move on to the implementation. It has to be an separately compilable object. The compiler will compile it, and call the macros implementation while it’s compiling the class in which our call site is. In this aliasFor method, we take the partially applied call, and “invoke” the macro (notice the macro keyword).

import language.experimental.macros // enable the macros language feature
 
import reflect.macros._
 
object Alias {
def aliasFor[P1, P2, T](delegate: (P1, P2) => T): T = macro alias_impl[T]
 
// ...
}

Now it’ll get a bit more interesting. Let’s start with looking at line 1 – the macro’s def .
It’s closely related to the invocation of the macro, see the delegate parameter here? It must be named the same way as the delegate parameter in aliasFor. If it isn’t or the signature here has more/less parameters than the signature of aliasFor – you’ll get a compile error, informing you that those must match.

Still in the signature, let’s look at c: Context, where’d that come from? You probably know – it’s the AST of our “surroundings”. You can call c.enclosingMethod or c.enclosingClass to get Tree‘s back from it. Also, it contains c.universe which may be thought of like “THE Scala entry point to reflection-land”, but since we’re still in compile-time, we get a compile-time universe. Using it we can create new AST nodes, or inspect anything that’s reachable from our code. It’s a big topic to wrap your head around, so it’s best to see the docs about it.

Next we’ll use this c to obtain what the method name of the one calling us is, and we can parse delegate to obtain the name of the method we want to delegate to. It will actually have the type information associated with it there by the way. Next we extract parameters that the delegator method has, and in the end, parse a small piece of scala code which basically is just the invocation of the target method + all the parameters it needs.

def alias_impl[T](c: Context)(delegate: c.Expr[Any]): c.Expr[T] = {
import c._
 
val tree = delegate.tree.children match {
case expr :: Nil =>
 
val methodName = extractDelegateMethodName(c)
 
val params = extractDelegatorParams(c).mkString(", ")
 
parse(s"""$methodName($params)""") // : c.Tree
 
case _ =>
c.abort(c.enclosingPosition, "alias macro should only be used on single method delegation")
}
 
c.Expr(tree)
}
private def extractDelegateMethodName(c: Context)(children: List[c.universe.Tree]): c.universe.Tree =
children
.map(a => a.children)
.flatten
.filter(_.isTerm) // PS: yeap I know this is getting ugly/long
.filterNot(a => a.isEmpty && a.toString == "aliasFor") // exclude the macro name
.head // first tree inside our macro
.children.head // the thing inside is partially applied - it should be just one method

Notice that if someone would pass in more than one partially applied method into our aliasFor method, we would go into the _ case in here, and abort execution. The c.abort method causes the compiler to stop, and print out the position where it failed, along with the message we supply (there’s an example failure on the github of this project).

Lastly, having a Tree we need to convert it to an Expr – as that’s what a macro should return. Exprs are bound to an universe, but since in compile time, there’s only one universe – we can go ahead with using this simple c.Expr factory (it wouldn’t be that easy in runtime).

Sources?

That’s it… a quick overview on how writing macros looks with scala. I definitely recommend reading up about Universes and Def Macros before you head out and rewrite everything to be a macro ;-) As for the use case of this project… Well, in theory it’s useful, but the win is rather small – so I wouldn’t advertise using it everywhere it may fit ;-) It is on maven central if you’re interested to try it out though.

All usage information and sources sources (available under the Apache 2 License) are on GitHub, so check it out:
https://github.com/ktoso/scala-macro-method-alias


 

Published at DZone with permission of Konrad Malawski, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)