Macro brainstorming

As I’m sure you’re all aware, with Scala 3 we have a new macro system that is focused on generating expressions only, as opposed to Scala 2 macros which were able to generate new definitions that changed the state of the program for everything else.

This has resulted in many requests for the addition of features similar to what annotation macros in Scala 2 provided.

I’m hoping that in this thread we can discuss the various uses of Scala 2 macros, and if there are alternatives that Scala 3 can provide us without having to introduce something like macro annotations in Scala 2.

To start with, there’s the generation of monomorphic service accessors for ZIO 2.

The usage of this macro is as follows:

@accessible
trait ServiceA {
  def method(input: Something): UIO[Unit]
}

generates

object ServiceA {
  def method(input: Something) =
    ZIO.serviceWithZIO[ServiceA](_.method(input))
}

In Scala 3, similar functionality can be achieved with programmatic structural types and regular macros:

trait ServiceA derives MonoService:
  def method(input: Something): UIO[Unit]

Service.instance[ServiceA] //produces ServiceBacking { def method(input: Something): ZIO[ServiceA, Nothing, Unit] }

The implementation of MonoService could be something like what’s shown in this repository: GitHub - markehammons/zio-macro-scala3-example

I’m hoping with this thread the community can settle on what are the current weaknesses of macros in Scala 3, what might need added to help things (like facilities for templating), etc.

Is this thread here to increase visibility for the issue at hand with current macros even more? :grinning:

I think the proposal for Export Macros is actually almost there already. It seems to solve the glaring hole with the missing possibility to export generated definitions form a macro scope. The facility to generate definitions at all is already included in the current macro features. Making those definitions visible to the “outside world” is the (only?) missing part, and export macros would solve this as I understand.

Also the discussion whether generated code should be purely “virtual” or written to disk seems to be related here. There seem to be divergent opinions on that topic.

I think there are some issues with current macros, but I think that there’s a lot that was done with the old macros that can be done with the current day macros as well.

I feel like macro annotations as they existed in Scala 2 gave too much power, and made boilerplate elimination very easy at severe cost to program legibility. Scala 3 macros as they currently exist still have a cost to program legibility, but it’s significantly reduced thanks to their inability invisibly mutate the global state of the program.

I’m hoping that this thread will be helpful in discovering what patterns actually needed the kind of power that macro annotations provided, as opposed to what patterns merely used macro annotations because they were there and convenient.

Re: generated code, if it’s creating statements it should always be written to disk. Tooling could be improved to inspect or view virtual code, but that’s always a second class solution.

With regards to the way macro annotations worked, I think that they should not come back. Something that creates code based on code that you’ve written as well as effects the code you write is way too complex IMO.

I concur!

Only that the proposed idea to mix generated code with hand written isn’t good, imho.

Looks like they are back https://github.com/lampepfl/dotty/pull/16392

There’s a whole big discussion in that thread about that feature. Currently it appears that it can emit new definitions that it can see, but not definitions that are visible outside of the scope of the macro.

See for example this comment on the macro annotation PR: Macro annotation (part 1) by nicolasstucki · Pull Request #16392 · lampepfl/dotty · GitHub