Common Lisp has good balance in this regard.
Meta-magic can include code-gen, but monkey-patching can include effectively doing ast-ast transforms to change the behaviour of existing code without having to actually edit the code.
I have just looked it up, and it indeed is a term [meaning something](https://en.wikipedia.org/wiki/Monkey_patch). Even meta-magic seem to be a term, but less defined seems like.
I don't think we need AST for monkey-patching, but it can be argued that each program can be converted to an AST. Anyway, less important.
The "monkey-patching" seem to basically mean anything that changes the runtime somehow. The example in Python on Wikipedia article just changes the value of a global variable. In Lisp we have many, many tools which can change code at runtime, I would even argue that Lisp is all about flexibility and "monkey-patching" since we already write AST and have the entire symbol table and the compiler available to us at runtime. Functions are just objects like any others and we can install any object in the function slot of an symbol, we can wrap it in our own function and install that one, everything is a list and dynamic in some way, so we can add slots to classes and structs if we want etc.
For meta-magic I found [this one article](https://betterprogramming.pub/the-magic-of-metaprogramming-7...) and [this one](https://codeburst.io/laziness-i-meta-programming-34ef4abdafc...) that seems to use the term in a sense as what we normally do with macros in Lisp or with templates in C++. The other one is a bit lengthy and I really didn't have patience nor time to read it in full length, I have just skimmed through it.
As I conclude, the terms mean what I meant, but with code generation, I mean not just classical generate some code to a file from another file; I mean transform the code in some way, during either the runtime or compile time.
There are cool tricks, but they come at a cost. Both for the reader of code, and the compiler of code.
Monkey patching is a feature common to many languages including Python, it did not originate in Ruby.
If I was having a punt I'd say it probably originates in Smalltalk or Lisp.
Wiktionnary
My Lisp Machine's operating system (MIT, end 70s onwards) is written in an object-oriented Lisp (using Flavors and later CLOS). Everything is open and everything can be changed at runtime. Mixins and similar stuff comes from there.
https://en.wikipedia.org/wiki/Flavors_(programming_language)
but Common Lisp has all the good stuff you need to manage different generations of objects under changing classes, not the least of which is jumping into the running system and examining the running state. warnings and errors about redefinition, maybe jump under a different package to manage generations. state and functions are separate, thanks to dynamic dispatch.
Ruby, on the other hand, rewrites the class. hope all the data and functions are forwards and backwards compatible! good luck.
A better approach is a language with phases. There is a description of the general idea on the home page of one of the authors: https://www.kent.ac.uk/computing/people/3165/kaleba-sophie
Their work seems to focus on discovering phases. IMO it's better if the language allows the programmer to express phases as a language concept. There are a few languages that support this. Racket is the best example I know of.
Many programs already implicitly have phases. For example, a typical web app has one phase at what is traditionally thought of at compile time, then another at startup when it reads it's configuration, and then another phase when it starts running. We smoosh together the last two phases into one "run time" because our languages usually don't support phases, but actually the configuration is known earlier: at deployment time. So we could run that phase as part of the CI / CD process and catch configuration errors earlier. Once you start thinking of phases you see them everywhere. They're in data science (loading data vs exploring data), front-end web apps ("hydration"), etc. I think it's a large productivity gain that is unexplored in commercial programming.
> IMO it's better if the language allows the programmer to express phases as a language concept.
Yeah, not just to make life easier for the compiler, but I suspect it'd also make it easier to read & reason about the code.
I mean, performance gains are nice but sometimes performance isn't really the bottleneck but reading & maintaining that unholy cocktail of application code, bash scripts, schema files & specs, build scripts, code generators, Dockerfiles, and Gitlab YAMLs is.
If you get a good compiler like sbcl, you can go a long way with just the language itself since the language itself offers a blend of scripting language qualities while being a compile language. Emacs Lisp can go long way too.