Posted on by & filed under scala.

One of the many things that bothers me about Java is the lack of compile-time checking.  I don’t want to have to run my program to know if the URL is well-formed, if a file exists, or if an email is valid.

Scala offers a solution.  Scala macros allow essentially arbitrary computations to run at compile time.  Let’s walk through the simple email example:

Getting started

First let’s define a simple email class with the toString() we are all expecting:

“case classes” are one of the best features of Scala.  They define immutable final classes with predictable default implementations for equality, hash values, and toString().  They also have pattern-matching support.

We can construct an Email like:

Adding readability

Scala also offers string interpolation so we can make these statements more readable:

Where at runtime Scala will essentially call an email function with the string.

We just need a little Scala magic for that to happen:

Now the magic happens

This may seem like adding a lot of complexity for a small improvement in readability.  But the real win comes from adding compile time-validation.

First we need to add some more magic with the macro keyword:

and then define the function that will be called at compile time

args will be a sequence of abstract syntax trees.  Scala macros work on the level of the AST so it is effectively impossible to produce a syntax error (unlike C/C++ macros).  c.Expr[Email] says that we will return an abstract syntax tree with type Email:

We can pattern match on the AST with Apply(_, List(Apply(_, rawParts))), where the underscores act as wildcards.  We only care about the first specific case.  If somehow things are different, we register an error with the compiler.

You can register errors with c.abort and warnings with c.warning.

When we have what we want, we can build the String AST fragment into a list of strings and their positions:

This step is necessary because there are some more complicated string interpolation use cases.  For now we only care when we have have the case of one string.

I hid the email validation logic in EmailParser (the details aren’t that interesting):

reify is a magic function that constructs an AST for literal Scala code.  And that’s it!

Getting the code and examples

Because of the quality of the Scala IDE, it is possible to develop this in real time.  You don’t even need to trigger a compile.

There are many cases where Java and other languages use strings as a proxy for distinctly non-string data (regular expressions are an obvious example).  So I built up some more validators for common Java types.

These examples and others are available in my source code.

Tags:

Comments are closed.