Annotation macros

I’m using macro annotations in my scala-js-preact library. This is the scala.js facade for the js library. Macro annotations help me to reduce boilerplate code in the public API. Resulting API looks a lot more clean and simple.
The first implementation of the library API was without macro annotations. It contains a few objects with factory methods for creating entities. And I was not happy with this API. It requires a lot more description in the docs and was a lot more complex. Macro annotations contain all this logic inside and at the same time its effect is still understandable (not too much magic).

I’m not sure that I can switch current API to another metaprogramming technique. As far as I understand, atm I have 2 alternatives:

  1. Using code generation with build tool. Actually, in my case, it could work, but I don’t want to provide my library with extra sbt plugin. It could confuse users and it makes the library less convenient for getting started.
  2. Compiler plugin. It could work too, but it has the same disadvantages as sbt plugin. And also, it makes the code less portable.
1 Like
  • I use the @dom and @fxml macro annotations to convert XML literal into HTML elements or FXML JavaBeans in Binding.scala
  • I use macro annotations to generate test suite in example.scala
  • For Binding.scala, I need the untyped tree to extract original for/yield expression, which will be messed up in typer due to implicit CanBuildFrom.
  • For example.scala, I need code generation for a class, not an expression.

Compiler plugins can achieve the same functionality. I need to rewrite the outer structure of those libraries as compiler plugin.

1 Like

Just wanted to chime in here to plea for macro annotations to not be abandoned. As mentioned before, libraries like freestyle allow to make complex patterns easy, otherwise requiring quite some boilerplate.

For my personal project i use them to build interfaces between scala (jvm) server and a scala jsclient, in the js client i use it to, for example, convert scala futures to js promises, scala lists to js arrays, so that i can easily call the methods in scala-js from from plain vanilla js.

Replacing this by compiler plugins would be way over my head, macro annotations are easy. Some other plain old codegen seems reinventing the wheel to me, it would be way more cumbersome, as it would require parsing the annotated code one way or another.

One of the big plusses (hate to mentioned it here) of scala over kotlin are its meta programming capabilities and the amount of boilerplate that they can reduce. Loosing macro annotations would hurt the language in this respect. I understand that making things work nicely in the ide is hard, but i’d be perfectly happy with macro annotations that have no ide support. Ide support for fancy scala is abysmal as is, red squiggly lines everywhere. In the case of annotation based code gen i’m not bothered by the ide borking, no big deal.

7 Likes

Hi,

towards what end do you use macro annotations?

We use them for grafter which is a dependency-injection library

why are macro annotations important for you and your users?

Because they allow us to modify the dependency graph of an application by basically doing nothing but add a new dependency to a case class. This is lots of productivity gained.

can you use alternative metaprogramming techniques such as
code generation scripts or compiler plugins to achieve the same
functionality? How would that refactoring impact your whitebox macro?

I have no idea if I would know how to replace them but I would be extremely annoyed to have to change this code, which would have a major impact on our applications and components.

towards what end do you use macro annotations?

I make use of frees.io annotations to eliminate the error prone boilerplate needed to explicitly define Free DSLs

why are macro annotations important for you and your users?

Without macro annotations using Free becomes too cumbersome and especially too complex for new developers. Macro annoations allow me to create powerful abstractions that elegantly separate the concerns of what my program is doing and how it is doing it, in a concise manner with very little overhead.

can you use alternative metaprogramming techniques such as
code generation scripts or compiler plugins to achieve the same
functionality? How would that refactoring impact your whitebox macro?

I’m not experienced enough for answer this. I’m not defining the macro annotations in frees.io, just making use of them.

1 Like

Whats the plan with scala meta @ast macros if macro annotations disappear? Will that switch to code generation? How would the type of AST node be specified? Via a tagging annotation? If infrastructure is provided by scalameta to support syntactic code generation directly perhaps as a pre-parse compiler plugin, I would fully support the decision to remove annotation macros. I still feel a lot is possible without semantic information, it would definitely keep the API smaller and easier to maintain. I should imagine it would also make it much easier for IDE’s etc. to analyze.

In fact, they actually got scala-meta macro annotations working nicely in the IDE at some point (it could expand on click, etc). So it’s not even like it’s unfeasible – it totally is.

1 Like

Definitely YES. My programming experience with Scala was significantly improved by macro annotation.

I’m using macro annotation for several purposes:

  • generate boilerplate codes for scalikejdbc
  • prepend stacktrace logging for each method in a class/object in play application, like
@logFunctions class FooController {
  def barAction(id: Int) = {
    Action { ??? }
  }
}

is conveted to

class FooController {
  def barAction = {
    play.api.Logger.debug(s"FooController.barAction(id = $id")
    Action { ??? }
  }
}
  • generate JSON formatter for play-json
  • and some others
  1. I don’t want to use code generation for two reasons:
    • (as someone mentioned before), code generation requires another sbt plugin or shell script which must be manually executed. That’s annoying.
    • Generated code COULD be modified, either intentionally or accidentally. It may become a cause of bugs which are difficult to find. If you use macro annotation, code is generated internally, thus the code is protected from any modifications.
  2. Compiler plugins are possible option. However, there must be a nice tool or library which supports plugin development process.

Hi all,

You will pleased to know work has started on scalagen a scalameta powered code generation library and SBT plugin.

All macro annotation’s that were supported in scalameta/paradise should be supported via codegen in scalagen. In fact, the api’s are so similar, that the bulk of your macro code, should work as code generation. But we can do more then a compiler plugin can.

The primary focus is performance (We all know Scala compiles slowly, lets not make it worse).

Please star https://github.com/scalameta/scalagen if you wish to follow the updates.

3 Likes

I don’t want to use code generation for two reasons:
(as someone mentioned before), code generation requires another sbt plugin or shell script which must be manually executed. That’s annoying.
Generated code COULD be modified, either intentionally or accidentally. It may become a cause of bugs which are difficult to find. If you use macro annotation, code is generated internally, thus the code is protected from any modifications.
Compiler plugins are possible option. However, there must be a nice tool or library which supports plugin development process.

@iTakeshi Some details on code gen for you…

Code generation is scala version and backend agnostic. This means the same generator will work for SN/SJS code with scala 2.10/2.11/2.12/2.13 and even Dotty. Provided those you dont use platform specfic features.

Compiler plugins are scala version specific and can cause issues with other compiler plugins.
Also if using a compiler plugin, any libraries used must be compatible with that compiler and backend.

Code generation has the downside of being build-tool specific. But for most annotation macro authors the ability to upgrade without waiting on a compiler plugin update, should far outweigh the downsides.

If generated code is modified (and you are using a plugin for a build tool such as SBT) it will be overriden. Although to be honest, it would suprise me if a generated file in the target directory was accidentally modified with any sort of frequency.

We could add a pre-parse compiler plugin to scalameta/scalagen. However at this stage, given the cross version requirements of that plugin. I would suggest that we do not do this unless absolutely necessary.

1 Like

Thanks @DavidDudson for your reply,

For the first point, I had forgotten the version-lock problem when I wrote the previous post. Now I understand code generation is superior to compiler plugins on this point.

For the second point, I should have explained about the situation I thought.
scalikejdbc has a code generator which generates some boilerplate to access RDB contents. A problem of this generator is that it generates code under the main src tree, thus generated code are not separated from other code and it can easily be modified. (And that’s why I re-implemented this feature by macro annotation to prevent generated code from being modified)
However, by reading @olafurpg’s comment on this issue, now I understand that code generated by scalagen will be put in another sbt project, so it won’t occur such kind of problems.

Thanks

Happens to me all the time when working on the scala/scala integration branch for the new collections. It currently uses a git submodule with the collection strawman whose sources get translated from strawman,collection to scala.collection. When I click through to a definition in IntelliJ I inevitably wind up editing the generated version of the file.

Interesting, I’ll keep jump to definition in mind.

You could generate the files with read only permissions set to prevent accidental editing while browsing code.

1 Like