I don't know this field so much.
Can someone explain what is possible in Scala 2.10 with macros, compared to what is possible in Java with compilation preprocessors, and tools like CGLIB, ASM, Byteman...?
[Update]: I've Tried to incorporate an example using Slick. It's difficult to sum up a lot of this stuff for a Java (non-Scala) audience.
Macros in Scala 2.10 bring full-blown meta-programming to the language proper, as a first-class citizen.
// we often do this:
log("(myList++otherList).size: " + (myList++otherList).size)
// just to log the string:
// "(myList++otherList).size: 42"
// Imagine log, if implemented as a macro:
log((myList++otherList).size)
// could potentially log both the EXPRESSION AND IT'S VALUE:
// "(myList++otherList).size: 42"
Could features like this generally be achieved via either textual preprocessing or byte-code manipulation in a safe and clean way?
Meta-programming comes down to code-generation at some stage, and unqualified, it is an umbrella term for a variety of techniques - in the interest of 'not having to write a bit of code yourself' - if one were to enumerate some of these techniques in some rough order of stage - from pre-compiled raw source to executing code, the list might look like:
(Notice I've omitted macro systems that run at compile time, more about them in the next paragraph.)
First, consider that all the above techniques essentially generate code - be it the generation/manipulation of plain textual source code or the generation/manipulation of byte code at run time.
What about macro systems? Macros that run at compile time and operate not on textual source code nor on compiled byte code, but at a far more valuable stage - they work on the AST of the program during compilation and the information available there and the integration with the compilation process renders them some powerful advantages. They are available both in dynamic languages (such as Lisp and Dylan) and in statically typed languages (Template Haskell and Scala 2.10's self-cleaning Macros).
Regarding Macros in Scala 2.10, off the top of my head, I'd say the most important advantage would be:
Type Safety: Compilation preprocessors and byte-code manipulation cannot take advantage of the type system. With macros - particularly compile-time macros -the kind that Scala 2.10 will have, the macro language is Scala itself with access to the compiler's API. Any kind of static analysis / checking of source code with full type information that is usually possibly only at compile time will be available to macros.
(Safe) Syntax Extension: Macros make it possible to adapt language constructs to better implement DSLs. A good example is Slick, a database library that lets you express SQL queries as type-safe Scala code:
Consider first, plain Scala list processing - no talk of databases or macros yet:
val coffees : List[Coffee] = // gotten from somewhere
// get from this list a list of (name, price) pairs also filtering on some condition
for {
c <- coffees if c.supID == 101
// ^ comparing an Int to an Int - normal stuff.
} yield (c.name, c.price)
// For Java programmers, the above is Scala's syntactic sugar for
coffees.filter(c => c.supId == 101).map(c => (c.name, c.price))
Slick, even the non-macro version, lets you treat database tables as if they were
Scala collections: This is how the non-macro version (Slick calls it the lifted embedding API) achieves the same, were Coffee
s an SQL table instead:
// WITHOUT MACROS (using enough of Scala's other features):
// Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101"
for {
c <- Coffees if c.supID === 101
// ^ comparing Rep[Int] to Rep[Int]!
// The above is Scala-shorthand for
// c <- Coffees if c.supID.===(Const[Int](101))
} yield (c.name, c.price)
Close enough! But here the ===
method is used to imitate ==
which cannot be used for the obvious reason that you can't compare the representation of an SQL column with an actual Int
.
This is solved in the macro version, if you have Scala 2.10 handy:
// WITH MACROS:
// Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101"
for {
c <- coffees if c.supID == 101
// ^ comparing Int to Int!
} yield (c.name, c.price)
So here macros are leveraged to provide the same syntax both for SQL as well as a plain Scala collection. It is the combination of type safety and macro hygiene in addition to the already present expressiveness and compositionality available in Scala that makes Macros attractive.
Also, consider this example from the link contributed by the other answer:
def assert(cond: Boolean, msg: Any) = macro impl
def impl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[Any]) =
if (assertionsEnabled)
// if (!cond) raise(msg)
If(Select(cond.tree, newTermName("$unary_bang")),
Apply(Ident(newTermName("raise")), List(msg.tree)),
Literal(Constant(())))
else
Literal(Constant(())
So that defines a macro assert
, whose usage will resemble a method call:
import assert
assert(2 + 2 == 4, "weird arithmetic")
Only, because assert
is a macro and not a method, the boolean expression 2 + 2 == 4
will be evaluated only if assertions were enabled. Note that there exists a shorthand to help with expressing AST's, but this example is hopefully clearer this way.
Last but not least - Scala 2.10 macros will be a part of Scala proper - Integrated into the standard distribution - as opposed to provided by some third-party library.
In addition to the points mentioned by Faiz, Scala macros are hygienic: they won't suffer from accidental capturing of identifiers. In particular, Scala macros are self cleaning: hygiene is achieved by reification where reification itself is a macro. For deeper insight on how this works see Scala Macros, a Technical Report.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With