What's the advantage of using Macros (instead of functions) is Scala?

406 views Asked by At

What are macros used for in Scala? What can be done with a Macro that cannot be done with a function?

I suppose one advantage are:

  1. The ability to parse the AST at the call point, but that's a fairly rare use case.
  2. The code doesn't result in an extra function call (modifying the AST directly instead), but modern compilers are pretty good at inlining functions.

Are there any other advantages I'm missing?

2

There are 2 answers

0
Julian Peeters On

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:

  1. read-in a case class definition as a string, and write back out
    updated strings
  2. 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")
}
0
Daenyth On

The advantage of macros is seen most when it prevents you from writing code in the first place.

Consider the case of play-json. I can define a case class, and the play-json formatter macros can create methods which convert my CC to and from json using the fields I've defined on the class.

The key thing here is that it's taking part of the source code which is not usually represented at runtime (the names of the variables) and it does compile-time (type-safe!) reflection on them to create specific functions instead of using runtime (unsafe) reflection.