I agree with Daenyth, macros have some modest but useful benefits for code-gen.
For arguments sake, consider a spectrum of what play-json could have done to generate code:
A) Two Dumb Runs:
- read-in a case class definition as a string, and write back out
updated strings
use the updated definition in the "real" run.
This is wonderfully simple at first, but clunky and not type-safe
B) Trees and Tasks: read the def in as a string, but use a runtime code-gen library like Treehugger to write code as trees, and an build-tool plugin to add the code-gen task to 'compile'
This gets us halfway type-safety, and sequential compilation by using a plugin offers at least an illusion of a single run.
C) Macros: use an experimental feature to read and write trees at compile time
Macros are fully type-safe, single run, and having everything happen in a single compilation means easily modifying generated code.
For Example
Say I use a code-gen library that adds def printType
to case class Record(x: Int)
, giving
case class Record(x: Int) {
def printType = println("Int")
}
Now say I want to add my own def goodbye
to the class as well:
Without macros: I could either
1) attempt to modify the output to
case class Record(x: Int) {
def printType = println("Int")
def goodbye = println("bye")
}
but then I encounter this is a generated file, DO NOT EDIT
printed at the top of the output file, reminding me that the file will be overwritten, so I'll either have to go to the hassle of toggling off code-gen somehow, or
2) attempt to modify the input to
case class Record(x: Int) {
def goodbye = println("bye")
}
but then the code-gen library probably won't see arbitrary code, so I would have to modify the code-gen library itself.
With macros: if the code-gen library relies on macros (and the library doesn't explicitly dispose of the class body), I can add my new def to the input
case class Record(x: Int) {
def goodbye = println("bye")
}
and it just works; my def is there, the generated def is there too.
case class Record(x: Int) {
def printType = println("Int")
def goodbye = println("bye")
}