Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • One piece of this that could be really nice in a normal language is if an LLM could generate a decent syntax highlighter or code-folding-spec or something for each "aspect." The idea of tooling to help us focus on one piece at a time is great, regardless of AOP.
  • I simply dislike AOP because it makes code much harder to follow. You suddenly have aspects which you need to be aware of at all times, worse aspects can interfere with each other in non-obvious ways. It breaks one of the core aspects of code, that you can do local reasoning.
  • Effect Systems are the answer to cross-cutting concerns in 2026.
  • The stuff i dreamed of doing but was smart enough to avoid in Java many years ago, is what I now use FP to do on the JVM, in scala, without any of the drawbacks of AOP. if i was stuck in java languiage on the jvm for some reason i could see the appeal of trying AOP now that LLM can assist with it. Thinking of the annoying stuff like setting up automated builds/compiler etc
  • Has anyone done AOP outside of Spring Framework? That's my only exposure and it feels very library level. Nothing I would use as a the primary way to structure the code.
  • Briefly with AspectJ
  • Yeah. I've done w/ Fody, PostSharp, HTTP Handlers, ASP.NET Middleware, Castle DynamicProxy/Interceptors, Temporal Interceptors, and various custom framework interceptors.
  • In the Java-verse it’s also do-able with Guice. I’ve tried it with Dagger but bailed (square peg / round hole).

    I think I prefer Springboot AOP, especially with SpEL.

    The term “cross cutting concerns” is thrown around a lot when discussing AOP. Took me a while to appreciate just how powerful it is in this context - sprinkle an AOP annotation here or there to avoid massive refactors in a large codebase, or avoid rewriting classes in a way that makes your classes themselves “cross cut concerns.”

  • The idea of this post - to write separate requirements for each concern and let LLM's integrate them - is much closer to the Leo version of literate programming, which allowed documents to be composed (roughly via scatter/gather operations mediated by sentinels).

    But the post entirely lacks the motivation for the AspectJ/AOP join point model: to have principled time/place for concern integration that was statically determined, type-safe, understandable to users -- and suitable for integration.

    > I've also always hated the specific mechanism that AOP chose to implement it with – something called the "join point model" which basically amounts to runtime pattern matching on a program's call stack and running some code every time a pattern matches.

    AspectJ's join point model is only dynamic where Java as a reference-based language could not support the static analysis. At compile-time, the "static shadow" of the pointcuts was calculated and implemented where staticly determinable; only the dynamic residue is deferred to runtime (e.g., is the caller to this method of type X?).

    Many of AspectJ's join points and type extensions - method call or execution, exception throwing, field access - largely have been adopted in many languages (python context managers, swift getter/setters/extensions), and the residue are a bit hard to use.

    But nothing really matches the power of pointcuts: to combine these predicates and the type-safe state-management - e.g., "when throwing an exception after a transaction, capture the span id along with the user id into a log message"

    AOP was great for the 7% of code that it was intended for, but was largely displaced as too complicated. Now with LLM's it's a decent hypothesis that with proper training LLM's could actually handle the more complicated but ultimately cleaner programming model - cleaner because it avoids the scattering of similar code which makes it hard to change.

    The key insight is that dominant concerns establish the basic structure of the application, leaving some important but residual aspects to fit themselves to the structure. That means the dominant structure must be suitable for the AOP integration (i.e., support the right pointcuts and type extensions); solve that and you've solved most integration issues. It's especially helpful for feature architectures, where you offer code in open-source to gain API adoption, paid for by closed-source library integrations with additional features.

  • Regarding Thomas first complaint about current implementations being runtime pattern matching, micronauts implementation of AOP is entirely compile time pattern matching.

    It's really neat:

    https://micronaut.io/2019/10/07/micronaut-aop-awesome-flexib...

  • I did a lot of work on intentional programming at Microsoft back in the 90s on C++, and also intentional software later.

    Our approach was quite a bit different to Kiczales at the time. We had "attribute providers" which were essentially compiler plugin DLLs.

    These plugins could register themselves at various parse points and add work like enzymes on the AST.

    So here we have a compile time attribute that writes all the boilerplate to add windowing support to a class. Adding message maps, hooking up the dispatch, life cycle etc.

    [Window] class MyWindow { };

    It worked, but as others pointed out, it suffers from combinatorial explosion, and issues with debug ability.

    That said, as long as you stick within well defined verticals it was still very useful and saved a lot of typing, and reduced cognitive load.

    I have thought quite a lot about modern versions of this using LLMs and I can see why the article notices a parallel to prompts or spec based designs.

    At some point I tried to make a version of the system we built almost 30 years ago, but using an LLM as the preprocessor instead of rigid AST hackery.

    It works a lot better, and you can approach a more continuous interpolations between intent blocks and generated code. Even the combinatorial problem largely disappears.

    I stopped working on it though because I couldn't really see anyone wanting to adopt a new language or some whacked out extensions these days.

    Maybe it does have its place though in certain fields. It might be worth having another go at it with fresh hindsight.

  • The Roslyn C# has a very developed system for injecting code at compile time in a very similar manner to what you described.

    On the 'experimental' side you have Jonathan Blow's language, Jai, which has integrated codegen (AST macros that look like code) + type inference into the language on a very deep level, and from the podcasts I've listened to with veteran C programmers, was that during the 90s, when the mass adoption of OOP began, but performance still mattered a lot - there were a lot of ideas around OOP that were different from how C++ ended up doing this.

    The most famous example I guess being COM, which is a C object model, that solves a bunch of issues that plague C++ to this day, such as reflection and code reusability, among others.

    But COM is C and entirely incompatible with C++. And the big issue imo with C++ is that like AOP here, it has elevated a bunch of arbitrary magic behavior to language level, that honestly could've been done very differently, and the C++ implementation often ends up worse (multiple inheritence is a typical example).

    A similar battle played out in Linux-land, with GTK creating its GObject system, which was roughly analogous to COM, and Qt opting to hack up C++, not unlike MFC.

  • I think there's a core of a good idea here, but as others have pointed out, letting the LLM be your "weaver" is going to be very tricky.

    It's possible that what you have here is an idea for what I consider to be eventually very likely, which is a computer languages still built for humans to be able to understand and debug it, but more primarily for LLMs to write it. Write a language designed to be an aspect-oriented language from the beginning. Equip it with the ability to run something like a language server and point it at a system and get all the "aspects" running and you might have something.

    But I'm skeptical of bodging this on to an existing language.

    One of the reasons I suggest making it a new language is that AOP was hampered by being able to use only what languages already supported. The need for a "weaver" is a smell anyhow. Something where the aspect code is the native representation and the "weaving" simply dissolves into the compilation process would not only make the whole thing more appealing in general, I think it would also allow for some things that even code generation might have found a challenge, like aspects that can maintain guarantees because the whole process is more aspect-aware and not broken by the embedded "payload" code written by a human.

    by jerf
  • I believe the sweet spot that makes it practical and reliable will be combining LLMs with formal verification, although I doubt current hardware is up to the task (yet).

    LLMs basically solve the classic Frame problem that prevented general problem solvers to be able to reason logically about the real world; however on their own they are utterly unpredictable and unreliable.

    However if the database of weights is merely used as a heuristic to guide the logical reasoning engine to promising regions of the problem space, and the program itself is written to specification directly by an inference engine, the result would be classic software not affected by hallucinations.

    The LLM could even help debugging the specifications by pointing out unclear or contradicting requirements, improving the process without compromising the integrity of the result.

  • re: dissolving into compilation, I think the machine/human separation has been at work for some time. Modern languages (e.g., rust, swift) are already pioneering tracking aspects like effects, lifetimes, regions, etc. and then using whole-program optimization at compile- and link-time, largely based on intermediate languages like LLM IR/SIL, which are surfaced as user-visible features when they compose well with other user-visible language features. LLM training on these languages makes them suitable for generative AI; I doubt LLM's could pick up some new language, particularly if it weren't analogous to existing ones.
  • > And the "weaver", to use the AOP term, is simply the LLM that generates the program from the documents.

    Oh HELL NO.

    The LAST thing you want is a non-deterministic process monkey patching your code.

  • At the end of the day aren't we all just non-deterministic process monkeys patching code?
  •   > The LAST thing you want is a non-deterministic process monkey patching your code.
    
    I'm not poking fun of you, but the irony here is that code-as-written is mostly a "suggestion" to modern compilers and JIT interpreters and the actual instructions emitted often look nothing like your ver-batim code.
  • That's where I got an immediate migraine.
  • This is, indeed, the next generation of AOP: they've managed to evolve it from "extremely complex and hard to understand runtime behavior" into "completely undefined runtime behavior". UB as a service. True innovation!
  • AOP is an interesting pattern but i've mostly tried to stay away from it mostly because:

    - code readability and maintainability takes a hit. If you don't know things are defined using AOP in files x,y,z you can read the code and miss a whole lot of things.

    - AOP implemented at runtime is a mess when you're trying to debug things

    So yeah, instead of having aop defined somewhere else to wrap a function call, i tend to prefer doing it explicitly transaction(function())

  • My problem with AOP has always been that it makes the simple case trivial and the hard case much harder.

    Looking at transactions: The 99% solution is trivial: Every service call is a transaction. AOP can save me a few lines for every method and things look much cleaner.

    But then comes the huge excel upload that is performance critical. Batch more service calls to fetch additional information in the background, commit every so-and-so records in a loop depending on the data size, do a custom roll-back if things fail.

    And suddenly this whole separation of concerns breaks down and creates a huge mess.

    The simple case saves a few minutes, the complicated case causes weeks of depression. Not a good tradeoff from my experience.

    An LLM adding to the confusion by only sometimes getting things right and explaining that the separate documents are always valid, except when they are not, well, sounds like a fun experience.

  • > My problem with AOP has always been that it makes the simple case trivial and the hard case much harder.

    Not always. DTrace, for example, is a tool to use AOP with programs and/or the OS kernel that makes the normal cases trivial (https://en.wikipedia.org/wiki/DTrace#Command_line_examples) and the hard cases possible (examples at https://github.com/opendtrace/toolkit)

    By carefully limiting what code you can inject, it prevents you from accidentally making hard cases hard to reason about.

  • Aspects are one of those categories of 'too powerful to be considered', or 'return value not worth the cost of troubles it can bring'.

    I completely agree with you, saved stuff is normally trivial, nightmare it can bring down the line makes those war stories that are fun to listen to, but certainly not fun to walk through. I simply skip them despite ie Spring offering powerful ways to manage transactions, logging etc. decoupled from places things are actually happening.

    I can imagine it working well in a disciplined team who consists of senior folks knowing their craft. Certainly I have never been part of a team with only such composition.

  • This is a retread of the 'animal-cat-dog' inheritance stuff we learned in our intro to OOP classes, where some people got together and put forward their own idea of programming as 'the way forward'.

    And me, like others have tried structuring our code like this, and failed, assuming the fault lay not with the idea itself but our skill level. Of course, by now it's kind of common knowledge that inheritance isn't a thing that can and should be used to solve every kind of problem.

    Same thing with AOP - it might be sometimes nice, but on the whole, elevating this to the language level seems to be counterproductive.

  • I always thought AOP was super cool, but also that it completely destroys readability and the ability to understand a codebase. I also think it's probably one of the worst concepts to embrace in the age of agentic coding. That would be like a foot missile.

    There are a limited number of patterns that absolutely do benefit from AOP though. The obvious one is logging. I don't think there's many though.

    Regardless, AOP is the last thing I'll be using these days. With LLMs I've been moving in the opposite direction with a focus on explicitness and correctness. Typed, compiled, non-null languages with clear, obvious, and well documented conventions.