Withers as procedural macros

Ron Pressler ron.pressler at oracle.com
Sat Nov 30 15:22:35 UTC 2024


Hi.

AST macros, i.e. compile-time subroutines that take an AST (or multiple AST terms) as an argument and return an AST were made famous in programming about 60 years ago. Since then, they’ve been adopted by many languages, some of which have been very well-liked by their relatively small userbase, but none of which, to date, has managed to amass a userbase sufficient for a leading mainstream programming language. AST macros are, indeed, very powerful, but their power is their main downside. They essentially allow creating new ad-hoc syntax, and too many people find that to be a bad idea, perhaps because they think it makes approaching a new codebase more difficult. Indeed, Java’s annotation processors were carefully designed to preclude them from being used like macros.

AST macros have more value in low-level languages that lack other metaprogramming capabilities, as macros can offer similar functionality in addition to introducing new syntax (which is liked by some but disliked by more). However, even in the low-level space, some modern programming languages, such as Zig, have tried to avoid macros and have shown that even their increased benefits for low-level languages can be obtained with weaker but less potentially harmful features.

In any event, Java’s metaprogramming abilities are powerful and are likely to increase with the addition of things like code reflection, but given that among the majority of programmers, those who use hugely popular mainstream languages like Java, the ability to add new syntax is viewed as a downside more than an upside. For Java to adopt *general-purpose* AST macros, my guess would be that prevailing opinions among mainstream programmers would need to shift or some new and significant benefit would be found to offset the downside.

However, if you like the power of macros, the Java Platform hosts one of the most beautiful and elegant programming languages that make extensive use of them — Clojure.

— Ron

> On 30 Nov 2024, at 14:41, Aaryn Tonita <atonita at proton.me> wrote:
> 
> I am very excited about withers, immutable objects are really inconvenient without such utility methods. I recently saw the distinction in the thread on '"With" for records and the default constructor' between withers and nominal constructors. I too have wished for such nominal constructors, and took notice that it isn't a priority. Fair enough.
> 
> But that got me thinking about why I wanted it and how else it could be done in user land and I recalled that JEP 468 makes an excellent point for the flexibility of use site creation of new values to avoid bloat on the declaring class. This explanation feels very much like the power of procedural macros which can generate the obvious boilerplate binding at the use site. If you were in fact adding procedural macro support (through something roughly equivalent to annotation processors) then the community could fill in the gaps for nominal creation and many other features. In either case withers seem to be work that the compiler has to perform at the use site and that can either be built in or plugged in.
> 
> In another thread you pointed out that you see this as a regularization feature and not a cool new feature which procedural macros certainly would be. However, the current syntax seems like it would be hard to retro fit to a procedural macro later on (if you would decide that later) while a different syntax might allow you to retrofit the work the compiler must do as a library procedural macro.
> 
> I figure you must have considered procedural macros at some point. Certainly java has some code generation functionality as is but to me it does feel quite different than procedural macros (or the higher order function capabilities of python annotations) feel in other languages. Annotation processors feel like a genuinely separate build step. In fact, powerful string templating could be delivered with such a feature where the procedural could read format!("{foo}")​ and close over the scoped variable foo or fail to compile when it is not in scope. So something like with!(point, x=localX, y=localY)​ could yield this and more like with!(point3D, point2D, z=localZ)​ for a nominal construction?.
> 
> That syntax definitely feels very un-java though.



More information about the amber-dev mailing list