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:
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.
Compiler plugin. It could work too, but it has the same disadvantages as sbt plugin. And also, it makes the code less portable.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.