Rhombus Language
rhombus-lang.org
rhombus-lang.org
It's the first time in more than 10 years that I actually feel like trying a new language. The last time was Elixir. Since then I had to use Lua (hobby project) and Python (work) but I don't enjoy them much. I would have skipped them if I hadn't have to use them. Before Elixir I enjoyed Ruby, before that it's been Perl 5 in the 90s. Everything else I used in that period and before was because I had to (C, Java, PHP, JavaScript/Node) and I skipped many other mainstream languages because they didn't look nice to work with (Go, Rust, TypeScript.) I still have to see what's writing and running a Rhombus programs looks like so I might discover that it's not so nice after all. I'm hopeful.
Typescript looks nice to work with but the tool chain is horrible (this isn’t really Typescripts fault though, more a synonym of it having to compile to JS).
Go looks horrible to work with (too simplified syntax) but is actually really nice because the tooling is (mostly) spot on and it’s simplified syntax weirdly helps with maintainability for large projects that have evolved over multiple years.
I guess this just goes to show how much personal preference can be a driving force behind our platforms of choice.
It's not even inherent to TS that the toolchain must be a morass of moving parts and multiple config files, as shown by Deno in contrast to Node.
The same goes fo Go. I have a mapping for if err != nil { ... } and the cursor is places inside the block.
I dont understand why people dont use this kind of thing for the most common programming constructs.
Coding software that works when conditions are met is easy. Coding software that works when things break is hard. So I want error handling to stand out because it forces errors to be handled visibly and properly.
All too often I see Python programs drop to a stack trace under normal execution or JavaScript return cryptic errors that don’t mean much to anyone bar the core maintainer. But I seldom see 3rd party Go software bomb out in quite the same way. And I think that’s in large part because Go doesn’t try to hide errors away as an inconvenience that we shouldn’t be looking at.
So I’ll agree Gos approach is ugly. But sometimes good software does just look ugly. And the way I see it, it’s the quality of the compiled software that matters more than the beauty of the combination of characters that built it.
The sad part is that people have been talking about this for years but Go team stubbornly refuses to make error handling more ergonomic.
It is, but it's also subtle, and if you want branches (especially sad-path branches) to be explicit, that's not a good thing.
Sure there are some Go specific stuff in there. But the whole thing reads more like someone preaching an irrational hatred for something rather than a fair breakdown of genuine footguns.
I’d be interested to know which libraries you’re working with where this has been a problem because I’ve only ever experienced one occasion where a 3rd party library panicked unnecessarily.
I guess this just goes to show how much personal preference can be a driving force behind our platforms of choice.
Technically, it provides an improved type system that offers the tooling to write safer code.
In practice, plenty of folks just rename the file extension and keep coding as they always did.
Really? Me and my team been using it for years with no problems whatsoever.
I do admit Go has an easily found foot gun: nil pointers. It's a small one though, in comparison to the original problems with nil pointers. More stubbing your toe than shooting your foot.
* To edit a Moodle backup file, I used Racket to unzip it, find the xml file, parse and filter some of the parts of the xml file and then zip the new version.
* To autoreply some emails, I used IMAP, then scrap some info from my webpage and then SMTP.
There is also a JSON library, but I never used it.
The only problem is that some libraries have no wrappers still, so instead of the expected snake_case name you must use the spear-case with some delimiter to make the parser happy. (Something like |spear-case| or {spear-case}, I should check the docs.) And if that annoys you too much, it's easy to write a macro with a rename transformer so you can use the nice snake_case name, it adds a negligible compilation time and no penalty at run time.
Edit: racket is a nice language, went my thinking, why give it a Python syntax? But I think they’ve answered that.
Rust seems pretty nice to work with (good DX) in it's domain: close to the metal programs were every tick counts. The main DX issue with it would be compile times.
Go's awful to me from a DX perspective as it is lacks proper error handling. Compile times are great though.
Maybe OCaml is a good fit for me (and you!). Fast compile times and good error handling.
TypeScript suffers too much from being a JS superset. JS has horrendous DX imho.
Also, I've come to prefer more FP.
BTW, I never liked the _N arguments. Too cryptic. They make me stop and think on details instead of just read the code and concentrate on the important stuff.
x.map {¦n¦ n + 3}.sum
is immediately clear but I guess that it's very subjective.The `next` keyword is interesting in Ruby. It can be used as an explicit return for blocks. In javascript, for comparison, there is a `continue` for standard loops, but non-standard blocks don't exist so they are functions and require the use of the return keyword (except for single-instruction arrow functions). And implicit returns or nexts simply don't exist in js.
x.map { it + 3 }.sum
which I'm still getting used toWhat if Racket and Python had a child? Rhombus represents much, much more to us Racketeers. It may quite possibly be the crown jewel of multi-paradigm meta-programming languages and most importantly, a possible solution to the 's-expression barrier'. Who knows, it might even make a dent in the LISP curse. And we were all hoping for this. We were all waiting for this. And so I applaud the enormous effort involved, and I hope it inspires people to try a different approach to programming, and to language in general. It represents an impressive achievement in terms of elegance, pragmatism/practicality and efficiency in a language built atop a very solid foundation, coming from one of the greatest lineage of programming languages in history.
Like many others, I have been waiting for a killer app, a real showcase of the power of language-oriented programming. I was secretly hoping Rhombus would become a catalyst to the production of something great. While the emergence of LLMs presents challenges, it doesn't have to seal the fate of amazing purely and proudly human innovations like this one. My GitHub account may be filled with Racket code few will ever use, but Rhombus gives me new inspiration.
Sure, the future of programming might involve neural networks programming CPUs and GPUs directly. We're getting there. Yesterday we were programming with cryptic machine language, today we program computers with increasingly natural syntax. Tomorrow we might just tell machines what we need. But that doesn't make what we're doing now any less significant or exciting.
We programmers aren't going anywhere just yet, and many of us will keep writing code because we love it, not just because we have to. And now we have this pinnacle of human ingenuity in the form of a beautiful language, a love letter to the art of programming by humans, for humans. Thank you.
[`shrubbery`], the replacement for s-exprs, is pretty interesting, expanding s-expr simply with grouping, and then a separate infix-pass on top. I've been playing with using it as the basis for a separate language; it's in an interesting place in the AST space, especially given the forethought put into macros.
[rhombus]: https://docs.racket-lang.org/rhombus/index.html
[shrubbery]: https://docs.racket-lang.org/shrubbery/index.html
There is a long history of identifying the problems and risks of copy-pasting, and trying to reduce it. I remember it was a selling point of Java in the early days. For all the efforts it doesn't seem to be going away. (all the boilerplate in Java probably didn't help)
I'd love to see a C-like Rhombus. A Chombus.
One thing I’d appreciate here is a “Why Rhombus?” page, even if the rationale is simply that it’s fun.
Edit: Turns out there’s a goals[1] page. Rhombus is trying to replace Lisp’s parenthesis-heavy syntax with something cleaner while keeping Racket’s powerful macro support.
Rhombus Goals links to: https://rhombus-lang.org/goal.html
- M-expressions (https://en.wikipedia.org/wiki/M-expression)
- Lisp 2 (https://en.wikipedia.org/wiki/LISP_2)
- Dylan (https://en.wikipedia.org/wiki/Dylan_(programming_language)
- Wolfram (https://en.wikipedia.org/wiki/Wolfram_Language)
- Julia (https://en.wikipedia.org/wiki/Julia_(programming_language))
However the large majority of Lisp folks end up using plain old Common Lisp and Scheme with their S-expressions, because it is easier, it is like telling written languages with symbols are harder than those with latin characters, it is only hard to read and write until one actually learns them.
For more details see the paper https://dl.acm.org/doi/pdf/10.1145/3622818 or watch the talk https://www.youtube.com/watch?v=hkiy1rmKA48
Thanks for the links.
- Liso (http://breuleux.net/blog/liso.html)
I have only recently become open to the idea that people might legitimately experience pain using an unfamiliar syntax.
For me, it is a non-issue. From Lisp to C to APL to Forth to Prolog, syntax was never an issue for me. I greatly enjoy learning languages with different approaches to syntax. It has never caused me pain. Only joy. Then again programming languages are my special interest.
It kind of easy to dismiss complains about syntax as intellectual lazyness. After all, learning Lisp-style syntax takes maybe a few hours tops, how can that be a problem? Syntax is just the easiest to criticize but would these people really learn the language if it had curly braces or is it just an excuse?
I don't know. I am the kind of person whose day gets ruined by an app minimally changing its UI so maybe I shouldn't be too judgy about syntax sensitive people.
Structure recognition should be pushed as far down in the subconscious as possible. Rainbow parens help, but it’s not nearly enough to stop other expression fragments from jumping into attention. Likewise clojure’s different bracket types for data structures, likewise the editor highlighting the paren matching the one at the cursor. Better than nothing, but incomparably worse than just having visually distinct syntax in the first place.
C-style is fine. Python-style, ML-style, SQL-style, BASIC, shell: all fine for structure recognition. But lisp is just a soup. Or a fog.
Same problem with elasticsearch queries, too.
Most "Lisp is unreadable" forum posting activity is just trolling by nonpractitioners. You can usually tell because it doesn't hit on the real readability issues, only the imaginary ones.
To read a Lisp dialect, you have to know what numerous words mean.
A seasoned Lisp coder cannot read the following, besides understanding its source code structure as data. I would guess that defcrunk foo defines something named foo, which is a crunk whatever that means. After that I have to be looking at the documentation of defcrunk (or asking AI).
(defcrunk foo (splat zing)
(:crom jang)
(:flit (burch culd)))
I suspect that a good proportion of Lisp is like this for newcomers.Sometimes people make new Lisp dialects because they don't like the words and they want to make up their own. It might not be their main reason but it figures in there. Someone else has to learn those, including people that already know an existing Lisp or two.
Also, my above defcrunk thing will parse in many a Lisp dialect; and I could make a macro to make it work in some way. Trolls about Lisp have really latched on to this one. Their leader, a front end web developer, wrote a little article about it about an imaginary curse ...
I do not find this myself, and it is a standard part of the design of a lot of commercial software: intentionally thinking about disabilities, impairments, and difficulties, and working on accommodating people who struggle with these things.
Examples:
* GUIs that flash the screen to signify a bell sounding for deaf users.
* Keyboard-operated GUIs for users with motor or sensory impairments who can't use pointing devices. (Blind users can't see a mouse pointer so can't use one.)
* UIs with alternate colour schemes for people with colour-blindness.
It has long been a source of irritation for me that FOSS tools are so resisitant to implementing this.
> I have only recently become open to the idea that people might legitimately experience pain using an unfamiliar syntax.
Good gracious. That is a surprise to me.
I love the _idea_ of Lisp and have written about it at length, but I find it, and APL, and many other languages, impenetrable.
For ordinary non-technical people, "algebra" is a synecdoche for "something that is really hard to understand".
Algebraic notation is just about the maximum level of mathematics that many non-specialists can handle. The idea of a letter or symbol standing for any number so that it is possible to reason about arithmetic without specifying the numbers being manipulated is brain-bending for the majority of people.
And yet, this is the sine qua non of programming languages. It's the first level you must master.
C is a very simple language. It has terse notations for common operations, such as incrementing a value.
It is not "low level". It vaguely represents the machine architecture of a PDP-11 from 50 years ago. It's nothing like any 21st century CPU. It is not "close to the metal". It is not "portable assembler".
But it's about as simple as a lot of people can handle, so thousands love it.
Go lower level -- to assembly language -- and you put off the majority of those who aren't genius-level.
Go higher-level, to matrix maths or to working directly in lists and ASTs, and you put off loads more.
Go sideways to working with stacks, like Forth, and you dissuade a load more.
Eliminate arithmetic precedence with RPN and you alienate thousands more. A few love their HP calculators, or Postscript, but most can't handle it.
And they might not know why they can't but they are angry when they are told to just ignore an unscalable wall. Put an impassable barrier in someone's path, tell them it will fade away and stop being noticeable, and what would you expect but anger and resentment?
This is why I argue that BASIC has great merit that is missing from things like Python. Not syntactic whitespace: Python mixes text with code in output, it forces beginners to deal with abstract concepts like "editors" and "files", plus the ubiquitous OOPS -- all things that are meaningless to beginners.
BASIC replaced this with the simple brilliance of _line numbers._
And so the pros hate it and take pride in hating it -- because contempt culture is endemic in software.
https://blog.aurynn.com/2015/12/16-contempt-culture
C, also, is extremely dangerous, and this appeals to the machismo of stereotypically nerdy geek types, who lack the conventional signs of machismo.
Thus, take C, make it safe by removing all the dangerous bits, but keep that terse syntax, and the result is Java -- loved by thousands of workman coders who are untrained but like an easy tool. Much of world business is glued together in Java.
But it lacks the element of danger so the macho nerds detest it. Contempt culture again.
Granted, if your only experience of BASIC is fond memories of the TRS-80 at your grade school, yeah any contempt for it will seem simplistic and poorly motivated. Maybe try having to depend on it for literally anything in a professional setting and you’ll have a more informed perspective.
Just as one can write spaghetti code in any language, one can equally write good clean code in any language.
2. Remember what the _B_ in BASIC stands for. Despite that it was used professionally, yes, but there is in any and all fields a need for easy tools for beginners to learn with.
https://www.fortressofdoors.com/take-the-pedals-off-the-bike...
As discussed here:
What happened here was that this payroll system of yours was written by people who saw clearly the danger of BASIC's many footguns and mitigated it the best way they could, by supplementing their code with vast quantities of english prose. This is not an argument in favor of BASIC.
> Just as one can write spaghetti code in any language, one can equally write good clean code in any language.
This is only half the truth. It's like saying it's equally possible to drive well or poorly in a 1990 Yugo as it is in a 2025 Accord. The car actually does matter in a lot of cases. I despise Python's tooling, but if one were to do a thoughtful rewrite of your old payroll system in Python, all those code comments would be completely unnecessary (it would be impossible to commit the kind of error that the comments were needed to warn against), and the code itself would be far easier to read and maintain.
As it happens, my boss, who wrote the app, quit, started his own company, and did rewrite it, from memory. He did it in QuickBASIC 4: it became a compiled binary app, in structured code. That made it more readable, sure: the point here being, it wasn't necessary to rewrite it in another unrelated language to get that win.
But QB was a cut-down version of the MS BASIC Professional Development System. It was intended as a pro tool, not as a thing for learners.
In other words:
• Don't mix up simple BASICs intended for beginners with pro-level ones.
• Don't assume that a more "serious" language means more readable code, because it doesn't.
• The flipside: don't assume that more readable code needs a more serious language. It doesn't.
None of your criticisms address what I'm trying to say, which is that BASIC was in its time a good tool for beginners, and the evangelists of, say, Python have failed to understand _why_ it was good at what it did. Python is _not_ a globally better thing.
Tools that are good for pros may be bad for beginners, just as tools for beginners can be bad for pros. This is surely not a stretch or a controversial statement.
That said mathematical code is one of the few cases where I don't like s-exp as much. Having easy access to "real" macros tends to make up for it, but I just don't find infix plus functional very convenient when number crunching. It's likely a skill issue on my part but that doesn't change the end result.
class Rect(left, top, right, bottom)
fun rect_like_to_rect(v): match v | Rect(_, _, _, _): v | {"LT": [l, t], "RB": [r, b]}: Rect(l, t, r, b) | {"TL": [t, l], "RB": [b, r]}: Rect(l, t, r, b)
rect_like_to_rect({"TL": [0, 2], "RB": [10, 5]}) // ⇒ Rect(0, 2, 10, 5)
Isn't this wrong? I'd expect to see Rect(2, 0, 5, 10) instead.
It also seems like "RB" was meant to be "BR".
class Rect(left, top, right, bottom)
fun rect_like_to_rect(v):
match v
| Rect(_, _, _, _): v
| {"LT": [l, t], "RB": [r, b]}: Rect(l, t, r, b)
| {"TL": [t, l], "RB": [b, r]}: Rect(l, t, r, b)
rect_like_to_rect({"TL": [0, 2], "RB": [10, 5]})
// ⇒ Rect(0, 2, 10, 5)
rect_like_to_rect({"LT": [0, 2], "RB": [10, 5]})
// ⇒ Rect(2, 0, 5, 10)
Not sure about the typo though, just wanted to make the code readable here since showing off code in the language being talked about is helpful. Two spaces before each line
creates a code block.If you want to see really innovative, readable and powerful language check out Red. While the language development seems to be ceased, the ideas (coming from old proprietary Rebol language) of working with code and data go much deeper than just macros: it has built-in DSL (called parse) for making DSLs on the fly and not just pre-runtime code manipulations. https://www.red-lang.org/
And there is XL language which might have even more powerful extensibility features, like adding types or pattern matching or asynchronous processing. Sadly I couldn't compile and try the only implementation it has, only judging by the docs explaining its ideas. https://xlr.sourceforge.io/
Moreover, compile-time macros fill a different role than run-time extensibility: macros can do some ahead-of-time static analysis and the compiler can emit much faster code with no runtime cost.
All I’m trying to say is, if you think Red is more impressive than Rhombus, then you are missing something. :) Rhombus is strictly more expressive than Red. Red might have some nice affordances, but there’s no reason why Rhombus couldn’t have them too.
(And I remember speaking with Matthew Flatt about an eDSL someone was building with the macro system—it was embedded regexes that could communicate with the host language about some of their internal structure; eg report number of capture groups for binding information or something like that. Again, this is all done in user-land, rather than part of the “core” of the language.)
One example I don't think you can reproduce with macros is something like this (I don't have Red at hand)
args: [x y] # these are literal items, like quote in lisps
code: [x * y]
my_func: func args code
print my_func 5 10 # prints 50Red 0.6.6 was just released: https://www.red-lang.org/2025/03/066-memory-management-impro...
The language is basically Racket, which is a Scheme at its core. There's very little in common with a language like Haskell.
I could only think of is they are both being a "typed lambda calculus" language with an ML-syntax.
I am somewhat troubled by tricks that are "too magic", like this example from front page:
class Posn(x, y)
fun flip_all([Posn(x, y), ...]):
[Posn(y, x), ...]
flip_all([Posn(1, 2), Posn(3, 4)])
// ⇒ [Posn(2, 1), Posn(4, 3)]
Why should the later Posns be flipped? That's certainly not what I would expect.(Then again being too magic is working well for Python, e.g. that a<b<c thing)
Here, the ... pattern combinator means 'match a list (segment) consisting of the pattern Posn(x,y) zero or more times, and because (free variables) x and y are under one ... combinator, bind x to a list of xs and y to a list of ys'. The template combinator ... means 'produce a list (segment) consisting of the template Posn(x, y) zero or more times, returning an error unless x and y are bound to lists. The reason I specified 'under one combinator' for the pattern part is that ellipses can be nested, resulting in contained pattern variables being bound to lists of lists etc. anyway, point is, this is all just compositional rules, with no spooky communication needed between pattern and template. I won't objectively argue that it's not magic, but it's at least not more magic than regexps.
It's trying to unify how macros produce syntax objects via `syntax-rules`-style pattern matching and ellipses with how regular pattern matching on values works, so there aren't two separate pattern languages (one for macros and one for everything else).
https://docs.racket-lang.org/guide/pattern-macros.html#(part...
I like the explanation there, and I think it gets me closer to my objection. I'm quite happy with the Kleene Star as a concept, but I don't think it corresponds to any usual use of ellipsis, nor is it really a list element on equal footing with the others.
The `[Posn(x, y), ...]` is a pattern that matches a list of positions. The `[Posn(y, x), ...]` is a template that produces a list of positions.
In the template the `Posn(y, x)` is followed by `...` so the same number of positions is produced as the common length of x and y.
If I had to guess:
fun flip_all([Posn(x, y), rest]):
[Posn(y, x), rest]
If I am correct then the only difference between flipping all and flipping just the first is `...` vs `rest` fun flip_first([Posn(x, y), more, ...]):
[Posn(y, x), more, ...] fun flip_all([Posn(x, y), & rest]):
You'd need to amend the logic of the function body to iterate or recurse over the remainder. You'd also ended up with a nested list using your version but you can use & again to splice in the result, so if you wanted a recursive version of that it would be something like (skipping the base case): fun
| flip_all([]): []
| flip_all([Posn(x, y), & rest]):
[Posn(y, x), & flip_all(rest)]
(not tested, don't have Rhombus available on this system but that should be correct)If you really want flip_first it'd be:
fun flip_first([Posn(x, y), & rest]):
[Posn(y, x), & rest] flip_one Posn(a,b) = Posn(b,a)
flip_first p:ps = flip one : ps
flip_all ps = map flip ps
To my taste that's a lot nicerAccording to the goals page, its other major feature is an extensible syntax. Why should I prefer it over, say, Scala? Scala has syntax macros, it runs on the JVM and can access Java's massive library of libraries, it has been used to implement large and complex projects like Spark and Lichess, what's that niche in which Rhombus can defeat Scala or Rust or Elixir?
- Scala: only really took off with Spark (had Akka before but wasn't really that popular)
- Python: might've died if not for numpy/pandas/ ML
- Ruby: Rails
- Typescript: V8/Node.js / javascript on the server-side
- Go: Google, Kubernetes (and even so it's questionable how popular it _really_ is)
- Swift: Apple, iOS apps (to a somewhat similar extent: Kotlin, for Android development).
- C#: I can't really name the "killer app", but... Microsoft. (to be fair the "killer app" itself might be the whole MS ecosystem integration)
There are a lot of decent languages (Clojure, D) that never got popular, despite their merits. Even Kotlin mentioned above is arguably a much better choice than Java on the server side. But it's not used that much on the server, even though it actually has the answer to "what problem does it solve" (easy way to write async code in JVM, which is not a small thing)
The author (M Flatt) is an incredibly gifted and productive programmer, btw.
I would dare to say that more people use Typescript in frontend than in backend projects, so basically Typescript killer "app" is the browser, because you are forced to use Javascript, but JS does not scale that good for mid, big projects.
Just like VSCode started as the Web IDE Monaco, written in this new Typescript thingie, before pivoting into Electron.
The next big killer application was Unity, which didn't even use Microsoft .NET.
Everything else is just marginal advantage: if you're a JVM shop, the existing experience of your programmers is more valuable than the speed of modern .NET or better pattern matching. But if you want to switch to making games, then switching to C# and/or C++ starts making sense.
It sounds like the authors see Lisp-style macros as uniquely powerful and are exploring how to bring similar meta-programming power to languages with syntax other than just s-expressions.
> what's that niche in which Rhombus can defeat Scala or Rust or Elixir
Good question. My read is that Rhombus isn't motivated by solving some "real world" problem and is instead an exploration in language design.
Here is a recent example https://blog.cloudflare.com/topaz-policy-engine-design/
https://github.com/evdubs?tab=repositories&q=&type=&language...
Windows/Linux/macOS: same as web, using cross-compilation[5]. Additionally for macOS, embedded in a Swift app and distributed as a .dmg[2] and on the Mac App Store[3]
iOS: embedded in a Swift app and distributed on the App Store[4].
[1]: https://docs.racket-lang.org/raco/exe-dist.html
[3]: https://apps.apple.com/us/app/franz-apache-kafka-client/id64...
[4]: https://apps.apple.com/us/app/podcatcher-podcast-player/id67...
[1]: https://github.com/dgoffredo?tab=repositories&q=&type=&langu...
[edit to clarify: I am not in college. I’m in my forties and have 25 years experience as an IT/programming/accounting generalist.]
fun all_same([str0, str, ...]):
all(str0 == str, ...) #lang rhombus
import rhombus:
only: fun ...
rename fun as fn
rename ... as …
open
fn all_same([str0, str, …]):
all(str0 == str, …) fun all_same(array(str0, str, ...)):
all(=(str0, str), ...)
Which taking could just as well be rendered only using English words for each token: let all-same of array of former eft latter eft more sam sam be
all of tie of former eft later sam eft more sam
Though "more" could also be replaced with "etcetera" or "etc" or "mo" for terseness or "and·son·on" for something more casual.Admittedly some of these words are not very frequently used in English, but that can actually be a big plus for a programming language keyword. On the other hand all of these terms have a meaning recorded in lexicographic works that would make perfect sense for the context they are placed in each position above. And taking the exact opposite of the approach to carry so much semantic in non-phonetic symbols, that is making them as semantically as void as a space or a comment in code parlance, they can just as well be introduced again:
【let〖all-same⦅of(array〚of[former„eft”latter„eft”⋮more⋮]sam〛)sam⦆be〗⧘:⧙ {
all⦅of(tie〚of[former„eft”later]sam〛„eft”⋮more⋮)sam⦆∎
}】◼
Ok, a bit far fetched, but it does illustrate well what this reversed approach regarding soundless symbols can open in term of additional scriptural cues compared to the one which also will bind them to some semantic and syntax constraints.https://en.wiktionary.org/wiki/mo#Adverb
I'm not clear what your asking about regarding the ellipses though.
[0] https://docs.racket-lang.org/guide/macros.html
[1] https://docs.racket-lang.org/reference/Reader_Extension.html
[2] https://docs.racket-lang.org/rhombus-meta/index.html
[3] https://docs.racket-lang.org/rhombus/Syntax_Objects_and_Macr...