I have used Racket in production for a decade and a half, with great success. Racket belongs to a sort of invasive species equivalent in languages, in the sense that once it has a hold on you, all other languages, old and new, vanish from your mind. The elegance of Scheme married with the power of hygienic macros does a great job at tricking your mind into believing Paul Graham's Hundred-Year Language idea again.
What 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.
The last app I worked on was an advanced Harmony/Flow(ex-shotgrid) bridge which let production staff manage Flow's database, upload assets and work on scenes without having to deal much with Flow's horrible UI. Over the years I have written hundreds of small tools to automate production and assist artists with their workflows in Harmony. Racket's built in UI library and supremely maintainable syntax made it very easy and fun to work with. I published quite a few apps and tools of variable quality at https://github.com/dexterlagan
The examples on the home page are a nice way to quickly show the features of a language. I'll check the documentation to see how to work with files, make HTTP calls, parse JSON.
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 & Rust don't "look nice to work with" because they force you to write maintainable code that doesn't just stop working because of a random runtime error. In my opinion, Go looks nice to work with but actually is a hidden monster full of footguns.
Curious to read that because I’ve always had the opposite opinion of the above:
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.
> 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).
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.
Simplified syntax helps with maintainability for large projects? You're talking about a "modern" language which had no generics for the better part of its life? The language filled to the brim with "if err != nil"? Go is horrible to work with.
The constant incantation of if err != nil is bad for my aging hands and frankly hurts even more when you see Rust's single-character error marshalling using ? operator.
I have aliases for these kinds of general things (in vim) for most languages i use. Eg. if im inside a typescript file i have a mapping produces if ($obj) { $ } and i tab to enter the next "$" to insert the correct things.
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.
I would suppose they do and are free to do it - however, liberally having to spread it all over the code hurts readability. I'm sure people can say in response to that after a while you stop noticing or that their favorite IDE can collapse it and so on. It can be excuses all the way down instead of admitting that it is a shortcoming of the language.
Most of the stuff on there is just talking about general programming mistakes. Eg overly nested code is a problem a developer can make in any language.
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.
The issue is you need to explicitly recover each goroutine just so a nil dereference or a panic from a library you have no control over doesn’t bring down the whole application. Maybe this is the way it has to be, but it is a little known fact that might bite you.
The only time a library should be panicking is if they’ve reached a state they cannot continue from.
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.
Exactly, Typescript doesn't stop working because of random runtime errors, it stops working because you didn't update the toolchain tangle last Thursday and then some configuration file got out of whack with the tsc defaults ;-)
Technically TypeScript doesn’t force you to anything other than writing valid JS code. Its design was great for wide adoption but also suffers from the JS part.
While Typescript has the gaping hole that is typing external data. Your code can't enforce that in TS, but in Go you're forced to.
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.
From Rhombus you can call all the Racket libraries. I used Racket in a few projects:
* 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.
I found Kotlin a "typed Ruby" (I found Ruby a "sane Perl").
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.
Interesting. I learned Ruby a long time ago because a Perl expert told me Ruby was getting good. I like Typescript because it's really easy to read and maintain without breaking anything. But you can't do this: `next x.map { _1 + 3 }.sum if sth`. I guess I should give Kotlin a try then.
After 20 years of Ruby I didn't know (or didn't remember) that next can have an argument. I looked it up. Thanks.
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.
Such details are so subjective even a same person could change his/her mind about it and change it back later...
I think they did a safe job of adding this _N feature though, since it's not allowed all the time. It felt like a missing feature because you could do map(&:to_i) but needed a parameter for something simple like map { |x| x == 5 }.
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.
Even the same customer has projects with Ruby ranging from 2.7 to 3.4 so I'm using the oldest possible syntax. The changes in keyword args hit hard some projects. Luckily not the one I'm working on most of my time.
This is racket's [rhombus], which might be an interesting second link here; it's a scheme, underneath, with the full power of Racket libs available.
[`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.
A while back I had made my own alternative to s-exprs that works with Racket. I have no idea if it still works, but I still think it looks nice, and at a glance I feel it's "purer" than shrubbery: http://breuleux.net/blog/liso.html
I really wish they had kept the old C-like Honu syntax rather that the Python-like Shrubbery. If Rhombus is supposed to be an educational language, copy-pasting and trying out new code is going to be a important part of ecosystem, and indentation based syntax is not ideal for that.
I guess. Truth be told I've taught hundreds, bordering on thousands of undergraduate intro courses in python, and students mostly figure it out after they configure tabs and spaces.
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)
C-parsing is still built-in [0], so whilst I don't think the standard library has a C-language, it's either really simple, or there'll be a few on the package manager.
The syntax looks clean, akin to Python, but with terser record types. This would make a nice config or an embedded language like Lua.
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.
Thanks. It’s nice to see at least a few in the FP community recognize that Lisp dialects, with their parenthesis-ridden S-expressions, are hard to read and write.
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.
It is hard to have empathy for a problem you don't have.
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.
It’s not about an unfamiliar syntax. S-exprs are objectively hard to read. The same shape for function calls, macros, blocks, and data means I can’t distinguish them by sight to detect the code structure. I have to do conscious paren matching.
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.
Nobody in Lisp does conscious paren matching. We rely on indentation. If you randomly deleted a parenthesis from some )))))), I would not notice. I would rely on the machine to tell me something is wrong.
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).
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 ...
Learning s expressions and the evaluation rule takes about 13 seconds. It’s one of the very first things in SICP. Learning how to use the loop macro, well, I’m still working on that.
> It is hard to have empathy for a problem you don't have.
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.
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.
For years I maintained an ERP system written in BASIC. It was legitimately terrible. The lack of lexical scoping and decent flow control made it nearly impossible to reason about for even simple changes. There’s nothing brilliant about line numbers and all-global variables, it’s literally one of the laziest, sloppiest design decisions you could make in a programming language.
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.
1. I helped maintain a payroll system written in GW-BASIC for a year or two. It was designed cleanly and well, with each module of code a separate file chain-loaded in from disk as a de facto overlay system. The variables that each module inherited were thoroughly documented in code comments at the top of each module, so it was clear what it would inherit in RAM and which you needed not to touch.
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.
The overlay system is an even worse paradigm than global variables within a program. And one I too am very familiar with.
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.
> 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.
Assuming it works equally well, people like what they're used to. On top of that programming in the AST requires you to think about things a bit differently which only widens the gulf.
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.
Looking through the examples, macros and pattern-matching, for me that would be impressive like 10 years ago.
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/
Looks like Red’s `parse` is just that… a parser. The pattern matching + “shrubbery” notation (see paper; tldr: syntax trees but flatter and more flexible via delayed parsing) are waaaay more general as they allow you to define totally new language grammars inside Rhombus itself. Building Red’s `parse` would be trivial in Rhombus.
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.)
I think it's clever how the logo is a different shape from Racket's to fit the name of the language, but they kept the same colors and kept the lambda in it.
This actually looks good. It's like a less-obtuse Haskell. At a glance the features seem to be just the right mix of functional programming paradigms and standard imperative programming.
The other day I saw someone describe writing Haskell as “programming with your elbows while listening to math rock”, which is awesome. I can see the need for a less-obtuse Haskell
Syntax-wise it looks a lot like Haskell to me, especially the pattern-matching, the variable declarations (with `::`), as well as the indent-based blocks.
I've been cautiously optimistic about Rhombus since the initial "Racket 2" controversy. I'd have preferred they went with something more like Wisp or Wraith, but it could be a lot worse.
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)
a big part of the racket project has been about establishing robust tools for transforming syntax. focusing on this use case has resulted in a much more generalized notion of pattern matching and template reconstruction than is typical. these kind of pattern/template combinators, as developed for racket/match and syntax-parse, might appear special-casey at first glance but from use i can attest that they are clean and compositional... they have the appearance of something you'd write informally on a whiteboard, but they really do just work, and the rules are fairly simple and easy to internalize.
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.
Are you familiar with `syntax-rules` based macros? Because this is roughly the same logic here.
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).
When the pattern `[Posn(x, y), ...]` matches a list of positions,
since `Posn(x, y)` is followed by `...` the `x` is bound to a sequence of first coordinates and `y` is bound to a sequence of second coordinates.
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.
Your version would expect a list of two elements, you use & to indicate that you're collecting the remainder of the list as in:
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):
I am going to play the devil's advocate. What problem does Rhombus solve? An approachable syntax is a necessary, but not sufficient requirement for mass adoption.
According 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?
This is not playing devil's advocate - this is a _VERY_ legitimate question. I'll go further and say that without a "killer app" (and/or strong corporate backer), a language doesn't typically take off nowadays, regardless how good it is ([edit] but to the point of another commenter - I do agree that "exploration" is a good enough reason; one doesn't need to write languages only with the goal of making them popular).
- 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)
I don't think they care so much as you think about 'takeoff'. It's mostly a research project in programming pedagogy. They're trying to make lisp features more accessible to students in universities. A takeoff would sure be appreciated, but the immediate goal is not a takeover of the industry.
The author (M Flatt) is an incredibly gifted and productive programmer, btw.
> Typescript: V8/Node.js / javascript on the server-side
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.
I agree; but I _think_ chronologically Node & server-side-JS came first? The driver is the same though: "larger codebases, running on top of Javascript" - and indeed nowadays probably the browsers are the things that run the larger codebases.
Typescript evolved from an internal project at Microsoft where they were trying to do their own GWT, but with .NET instead of Java, the browser was definitly first.
Just like VSCode started as the Web IDE Monaco, written in this new Typescript thingie, before pivoting into Electron.
I disagree strongly with Kotlin being a better choice than Java. Kotlin solves problems Java had 14 years ago while bringing along totally unnecessary syntax complexity. Moreover it is slow to compile and is essentially a proprietary language.
C#: ASP.NET Core (a slimmer, faster Spring Boot), EF Core (better Prism, jOOQ, etc.), very flexible build targets (JVM-style, self-contained JIT and AOT binaries), easy integration with F#, good pattern matching, access to low-level features only rivaled by Swift (although Swift has its own set of issues). Far better performance ceiling than all the languages here on the list.
You forgot async/await. But all that wasn't what propelled C# to popularity, and I say that as someone who's been writing it since 2002. It was an army of VB6 and Delphi line-of-business programmers migrating to the new stack and enterprises running Windows.
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.
> Many newer languages include a macro system to enable extensibility, but few would argue that the new batch of macro systems have achieved the expressiveness and fluidity of macros as they exist within the Lisp tradition, which includes Racket.
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.
Add to this integration with an entire family of languages. Racket permits you to jump between different syntaxes. The closest thing that immediately comes to mind is writing inline C in Chicken or Gambit Scheme and that's far clunkier. Hy (embeds in python) also comes to mind but Python often seems rather slow.
I have one day of college under my belt and use Racket for all my personal projects, as well as in a few areas related to ERP systems professionally. I have found no compelling reason to use Typed Racket so far.
[edit to clarify: I am not in college. I’m in my forties and have 25 years experience as an IT/programming/accounting generalist.]
I've used it for some command line [tools][1]. Nothing running on production. Mostly programs that process programs or markup. I haven't used Typed Racket.
Web: create an executable distribution[1] and ship it to a server.
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].
I tried, it's a really nice language with almost no libraries so you end up reinventing the world every new thing you do. It's not really practical compared to arguably worse languages with better ecosystems
So basically, assuming no special constructor for array, this is
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:
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.
That time would only be enough to read the overview, go through the examples, click on the "uniquely customizable" link hoping to see the answer and then be flooded with the garbage digital tokens of the scientific world - 100+ references...
True. If you are new to Lisps it will require substantially more than 5 minutes to learn about the different types of macros. For Racket in general you might start here.[0][1] For Rhombus probably here.[2][3]
I'm not clear what your asking about regarding the ellipses though.
It's static typing with inference. Essentially what you have in Haskell/OCaml/F# where you declare a variable `x` through a let-binding witout specifying its type (`let x = something`), and the compiler analyses `something` and infers the type of `x`.
See also https://github.com/mflatt/shplait, which is a language with (1) the HM type system and (2) the same syntax that Rhombus uses. The language itself is implemented in Rhombus.
Absolutely: you can make that with some macros and using type annotations. I do not know how difficult can it be, but surely there are thing already done.
This is good to see, but I think too much effort is being used to maintain the prefix style function application. As a result a significant amount of the novelty is just mapping syntax from new to old. Special cases, special cases everywhere, even in an implementation like this which is specifically attempting to allow you to make your own "special" cases.
I have used Racket in production for a decade and a half, with great success. Racket belongs to a sort of invasive species equivalent in languages, in the sense that once it has a hold on you, all other languages, old and new, vanish from your mind. The elegance of Scheme married with the power of hygienic macros does a great job at tricking your mind into believing Paul Graham's Hundred-Year Language idea again.
What 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.
Can you tell me more about what you're building with Racket? Warms my heart to hear about/see Racket being used in production.
The last app I worked on was an advanced Harmony/Flow(ex-shotgrid) bridge which let production staff manage Flow's database, upload assets and work on scenes without having to deal much with Flow's horrible UI. Over the years I have written hundreds of small tools to automate production and assist artists with their workflows in Harmony. Racket's built in UI library and supremely maintainable syntax made it very easy and fun to work with. I published quite a few apps and tools of variable quality at https://github.com/dexterlagan
The examples on the home page are a nice way to quickly show the features of a language. I'll check the documentation to see how to work with files, make HTTP calls, parse JSON.
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 & Rust don't "look nice to work with" because they force you to write maintainable code that doesn't just stop working because of a random runtime error. In my opinion, Go looks nice to work with but actually is a hidden monster full of footguns.
Curious to read that because I’ve always had the opposite opinion of the above:
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.
> 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).
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.
Simplified syntax helps with maintainability for large projects? You're talking about a "modern" language which had no generics for the better part of its life? The language filled to the brim with "if err != nil"? Go is horrible to work with.
I’m not sure about Go being full of footguns, but for one, a panic in any goroutine forcibly terminates the entire application.
The constant incantation of if err != nil is bad for my aging hands and frankly hurts even more when you see Rust's single-character error marshalling using ? operator.
I have aliases for these kinds of general things (in vim) for most languages i use. Eg. if im inside a typescript file i have a mapping produces if ($obj) { $ } and i tab to enter the next "$" to insert the correct things.
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.
I would suppose they do and are free to do it - however, liberally having to spread it all over the code hurts readability. I'm sure people can say in response to that after a while you stop noticing or that their favorite IDE can collapse it and so on. It can be excuses all the way down instead of admitting that it is a shortcoming of the language.
https://100go.co/ has a great list of Go footguns
Most of the stuff on there is just talking about general programming mistakes. Eg overly nested code is a problem a developer can make in any language.
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 think that's the intention of a panic.. however there is still recover if you need to "catch" it.
The issue is you need to explicitly recover each goroutine just so a nil dereference or a panic from a library you have no control over doesn’t bring down the whole application. Maybe this is the way it has to be, but it is a little known fact that might bite you.
The only time a library should be panicking is if they’ve reached a state they cannot continue from.
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.
JS isn’t breaking, it’s the layers between there and Typescript.
Exactly, Typescript doesn't stop working because of random runtime errors, it stops working because you didn't update the toolchain tangle last Thursday and then some configuration file got out of whack with the tsc defaults ;-)
Typescript is like C++ and Objective-C for C.
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.
Technically TypeScript doesn’t force you to anything other than writing valid JS code. Its design was great for wide adoption but also suffers from the JS part.
> Go looks nice to work with but actually is a hidden monster full of footguns.
Really? Me and my team been using it for years with no problems whatsoever.
While Typescript has the gaping hole that is typing external data. Your code can't enforce that in TS, but in Go you're forced to.
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.
Do you feel the same way about English?
From Rhombus you can call all the Racket libraries. I used Racket in a few projects:
* 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.
I was skeptical when I heard “Python-like Racket syntax,” but this certainly looks like the Python I wish I was writing.
Edit: racket is a nice language, went my thinking, why give it a Python syntax? But I think they’ve answered that.
I think they say "Python-like syntax" because "ML-like syntax" is too big a turnoff ;)
Hadn’t thought of it this way, but yes, I suppose ML is the Python I wish I was writing!
I found Kotlin a "typed Ruby" (I found Ruby a "sane Perl").
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.
Did you ever see Crystal? It's more or less a typed Ruby. I've heard that you can port some code directly.
https://crystal-lang.org/
YesI have. But I need to get work done so I rather use a language with a larger ecosystem.
Also, I've come to prefer more FP.
Interesting. I learned Ruby a long time ago because a Perl expert told me Ruby was getting good. I like Typescript because it's really easy to read and maintain without breaking anything. But you can't do this: `next x.map { _1 + 3 }.sum if sth`. I guess I should give Kotlin a try then.
After 20 years of Ruby I didn't know (or didn't remember) that next can have an argument. I looked it up. Thanks.
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.
is immediately clear but I guess that it's very subjective.Such details are so subjective even a same person could change his/her mind about it and change it back later... I think they did a safe job of adding this _N feature though, since it's not allowed all the time. It felt like a missing feature because you could do map(&:to_i) but needed a parameter for something simple like map { |x| x == 5 }.
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.
In 3.4 you can now do
which I'm still getting used toEven the same customer has projects with Ruby ranging from 2.7 to 3.4 so I'm using the oldest possible syntax. The changes in keyword args hit hard some projects. Luckily not the one I'm working on most of my time.
You also find `it` in Kotlin.
You ever heard of Crystal, Red, XL?
This is racket's [rhombus], which might be an interesting second link here; it's a scheme, underneath, with the full power of Racket libs available.
[`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
A while back I had made my own alternative to s-exprs that works with Racket. I have no idea if it still works, but I still think it looks nice, and at a glance I feel it's "purer" than shrubbery: http://breuleux.net/blog/liso.html
I really wish they had kept the old C-like Honu syntax rather that the Python-like Shrubbery. If Rhombus is supposed to be an educational language, copy-pasting and trying out new code is going to be a important part of ecosystem, and indentation based syntax is not ideal for that.
I guess. Truth be told I've taught hundreds, bordering on thousands of undergraduate intro courses in python, and students mostly figure it out after they configure tabs and spaces.
A valid point.
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.
lol at Chombus
I’ve never seen anybody learn much by copy pasting. If you want to learn, you must put fingers to keys.
C-parsing is still built-in [0], so whilst I don't think the standard library has a C-language, it's either really simple, or there'll be a few on the package manager.
[0] https://docs.racket-lang.org/c-utils/parsing.html
C was my first language and braces confused the shit out of me. I found Python’s syntax much more intuitive when I learned it.
The syntax looks clean, akin to Python, but with terser record types. This would make a nice config or an embedded language like Lua.
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.
[1]: https://rhombus-lang.org/goal.html
> Why a new language? See Rhombus Goals.
Rhombus Goals links to: https://rhombus-lang.org/goal.html
Graaah, totally missed it. Thanks!
here is the "goal" page: https://rhombus-lang.org/goal.html (with related paper & talk)
Thanks. It’s nice to see at least a few in the FP community recognize that Lisp dialects, with their parenthesis-ridden S-expressions, are hard to read and write.
This is nothing new, see
- 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.
This is a new approach :)
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
Sure, and great that other attempts are being made, only pointing out to the OP, that there is previous work on this domain.
Thanks for the links.
Just going to throw my own (old) attempt in here.
- Liso (http://breuleux.net/blog/liso.html)
Also sweet expressions [0]
[0] https://dwheeler.com/readable/sweet-expressions.html
It is hard to have empathy for a problem you don't have.
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.
It’s not about an unfamiliar syntax. S-exprs are objectively hard to read. The same shape for function calls, macros, blocks, and data means I can’t distinguish them by sight to detect the code structure. I have to do conscious paren matching.
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.
Nobody in Lisp does conscious paren matching. We rely on indentation. If you randomly deleted a parenthesis from some )))))), I would not notice. I would rely on the machine to tell me something is wrong.
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).
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 ...
Have you looked at idiomatic Racket code? You can freely switch between () [] and {} which makes things a lot cleaner.
Learning s expressions and the evaluation rule takes about 13 seconds. It’s one of the very first things in SICP. Learning how to use the loop macro, well, I’m still working on that.
> It is hard to have empathy for a problem you don't have.
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.
For years I maintained an ERP system written in BASIC. It was legitimately terrible. The lack of lexical scoping and decent flow control made it nearly impossible to reason about for even simple changes. There’s nothing brilliant about line numbers and all-global variables, it’s literally one of the laziest, sloppiest design decisions you could make in a programming language.
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.
1. I helped maintain a payroll system written in GW-BASIC for a year or two. It was designed cleanly and well, with each module of code a separate file chain-loaded in from disk as a de facto overlay system. The variables that each module inherited were thoroughly documented in code comments at the top of each module, so it was clear what it would inherit in RAM and which you needed not to touch.
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:
https://news.ycombinator.com/item?id=42697467
The overlay system is an even worse paradigm than global variables within a program. And one I too am very familiar with.
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.
> 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.
I'd say it's more like "we've been teaching Scheme for decades, and we just have to admit that most people don't like S-expressions."
Assuming it works equally well, people like what they're used to. On top of that programming in the AST requires you to think about things a bit differently which only widens the gulf.
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.
Looking through the examples, macros and pattern-matching, for me that would be impressive like 10 years ago.
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/
Looks like Red’s `parse` is just that… a parser. The pattern matching + “shrubbery” notation (see paper; tldr: syntax trees but flatter and more flexible via delayed parsing) are waaaay more general as they allow you to define totally new language grammars inside Rhombus itself. Building Red’s `parse` would be trivial in Rhombus.
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.)
From the 2nd example:
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".
If you indent each line by, uh, something (I use 4 spaces), HN gives you code formatting:
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 are all you need. There are only two formatting rules on HN so it's easy to remember: * around text adds emphasis emphasis.
I guess it's a typo and the second line should say "BR". I just send a PR to fix it.
I think it's clever how the logo is a different shape from Racket's to fit the name of the language, but they kept the same colors and kept the lambda in it.
This actually looks good. It's like a less-obtuse Haskell. At a glance the features seem to be just the right mix of functional programming paradigms and standard imperative programming.
The other day I saw someone describe writing Haskell as “programming with your elbows while listening to math rock”, which is awesome. I can see the need for a less-obtuse Haskell
It also involves sending values forwards and backwards in time using Tardises occasionally
Less obtuse Haskells have existed for years: Standard ML, OCaml, and F#.
What is the relation to Haskell?
The language is basically Racket, which is a Scheme at its core. There's very little in common with a language like Haskell.
Syntax-wise it looks a lot like Haskell to me, especially the pattern-matching, the variable declarations (with `::`), as well as the indent-based blocks.
It's usually called ML-syntax. SML, OCaml, Elm, PureScript, and many more have used this.
So it's not PURE!
Yeah, it doesn't have the one thing that makes Haskell Haskell.
It's also strict rather than lazy by default. So it doesn't have the two things that make Haskell Haskell.
Actually it started with Miranda.
Which in turn started with https://en.wikipedia.org/wiki/SASL_(programming_language)
> What is the relation to Haskell?
I could only think of is they are both being a "typed lambda calculus" language with an ML-syntax.
I've been cautiously optimistic about Rhombus since the initial "Racket 2" controversy. I'd have preferred they went with something more like Wisp or Wraith, but it could be a lot worse.
I am somewhat troubled by tricks that are "too magic", like this example from front page:
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)
a big part of the racket project has been about establishing robust tools for transforming syntax. focusing on this use case has resulted in a much more generalized notion of pattern matching and template reconstruction than is typical. these kind of pattern/template combinators, as developed for racket/match and syntax-parse, might appear special-casey at first glance but from use i can attest that they are clean and compositional... they have the appearance of something you'd write informally on a whiteboard, but they really do just work, and the rules are fairly simple and easy to internalize.
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.
I understand what it's doing, I just don't like that it does it.
Are you familiar with `syntax-rules` based macros? Because this is roughly the same logic here.
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).
It's not magic. It's pattern matching.
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.
That syntax suggests the first pair will be flipped, and the rest will just be as they were, i.e. more like flip_first
When the pattern `[Posn(x, y), ...]` matches a list of positions, since `Posn(x, y)` is followed by `...` the `x` is bound to a sequence of first coordinates and `y` is bound to a sequence of second coordinates.
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.
What would be the corresponding syntax for matching and flipping only the first Posn?
If I had to guess:
If I am correct then the only difference between flipping all and flipping just the first is `...` vs `rest`Your version would expect a list of two elements, you use & to indicate that you're collecting the remainder of the list as in:
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): (not tested, don't have Rhombus available on this system but that should be correct)If you really want flip_first it'd be:
In Haskell:
To my taste that's a lot nicerTypical pattern matching like in Haskell or Prolog wouldn't do this.
It makes perfect sense if you're used to the pattern matching of Scheme and Racket syntax macros.
Care you to elaborate on Racket 2 controversy? I don't follow the language, but I'm aware of it, so I didn't learn about the controversy.
e.g. https://beautifulracket.com/appendix/thoughts-on-rhombus.htm...
Interesting to see a new language like Rhombus popping up. Always curious to see what innovative features it brings to the table.
I am going to play the devil's advocate. What problem does Rhombus solve? An approachable syntax is a necessary, but not sufficient requirement for mass adoption.
According 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?
This is not playing devil's advocate - this is a _VERY_ legitimate question. I'll go further and say that without a "killer app" (and/or strong corporate backer), a language doesn't typically take off nowadays, regardless how good it is ([edit] but to the point of another commenter - I do agree that "exploration" is a good enough reason; one doesn't need to write languages only with the goal of making them popular).
- 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)
I don't think they care so much as you think about 'takeoff'. It's mostly a research project in programming pedagogy. They're trying to make lisp features more accessible to students in universities. A takeoff would sure be appreciated, but the immediate goal is not a takeover of the industry.
The author (M Flatt) is an incredibly gifted and productive programmer, btw.
> Typescript: V8/Node.js / javascript on the server-side
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.
I agree; but I _think_ chronologically Node & server-side-JS came first? The driver is the same though: "larger codebases, running on top of Javascript" - and indeed nowadays probably the browsers are the things that run the larger codebases.
Typescript evolved from an internal project at Microsoft where they were trying to do their own GWT, but with .NET instead of Java, the browser was definitly first.
Just like VSCode started as the Web IDE Monaco, written in this new Typescript thingie, before pivoting into Electron.
I disagree strongly with Kotlin being a better choice than Java. Kotlin solves problems Java had 14 years ago while bringing along totally unnecessary syntax complexity. Moreover it is slow to compile and is essentially a proprietary language.
C#: ASP.NET Core (a slimmer, faster Spring Boot), EF Core (better Prism, jOOQ, etc.), very flexible build targets (JVM-style, self-contained JIT and AOT binaries), easy integration with F#, good pattern matching, access to low-level features only rivaled by Swift (although Swift has its own set of issues). Far better performance ceiling than all the languages here on the list.
You forgot async/await. But all that wasn't what propelled C# to popularity, and I say that as someone who's been writing it since 2002. It was an army of VB6 and Delphi line-of-business programmers migrating to the new stack and enterprises running Windows.
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.
I forgot about Unity. Yes you're absolutely right.
> Many newer languages include a macro system to enable extensibility, but few would argue that the new batch of macro systems have achieved the expressiveness and fluidity of macros as they exist within the Lisp tradition, which includes Racket.
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.
Add to this integration with an entire family of languages. Racket permits you to jump between different syntaxes. The closest thing that immediately comes to mind is writing inline C in Chicken or Gambit Scheme and that's far clunkier. Hy (embeds in python) also comes to mind but Python often seems rather slow.
Does anyone use Racket outside academia? Is it production ready (or friendly)? Especially Typed Racket.
Racket has been used outside academia for years. Racket has been production ready for years.
Here is a recent example https://blog.cloudflare.com/topaz-policy-engine-design/
Yes. I've been using it for 8 years. It has been production ready that whole time. I am not a user of Typed/Racket; the contracts system works well.
https://github.com/evdubs?tab=repositories&q=&type=&language...
I have one day of college under my belt and use Racket for all my personal projects, as well as in a few areas related to ERP systems professionally. I have found no compelling reason to use Typed Racket so far.
[edit to clarify: I am not in college. I’m in my forties and have 25 years experience as an IT/programming/accounting generalist.]
I've used it for some command line [tools][1]. Nothing running on production. Mostly programs that process programs or markup. I haven't used Typed Racket.
[1]: https://github.com/dgoffredo?tab=repositories&q=&type=&langu...
I’ve used it for all kinds of production stuff over the years, from web to desktop and mobile apps.
Interesting, how do you distribute/publish those apps?
Web: create an executable distribution[1] and ship it to a server.
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
[2]: https://franz.defn.io/
[3]: https://apps.apple.com/us/app/franz-apache-kafka-client/id64...
[4]: https://apps.apple.com/us/app/podcatcher-podcast-player/id67...
[5]: https://docs.racket-lang.org/raco-cross/index.html
I tried, it's a really nice language with almost no libraries so you end up reinventing the world every new thing you do. It's not really practical compared to arguably worse languages with better ecosystems
It has a lot of libraries. Can you give some examples of the gaps you are talking about?
How hard would it be to uniquely extend the language to make *fun* into *fn* and *...* into a single letter *…*?
The easiest way would be to rename the imported names
So basically, assuming no special constructor for array, this is
Which taking could just as well be rendered only using English words for each token: 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:
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
https://en.wiktionary.org/wiki/sam#Adverb
https://en.wiktionary.org/wiki/eft#Adverb
Very easy. You could do it yourself after 5 min of reading ;-)
That time would only be enough to read the overview, go through the examples, click on the "uniquely customizable" link hoping to see the answer and then be flooded with the garbage digital tokens of the scientific world - 100+ references...
True. If you are new to Lisps it will require substantially more than 5 minutes to learn about the different types of macros. For Racket in general you might start here.[0][1] For Rhombus probably here.[2][3]
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...
The ellipses was about replacing 3 ascii symbols 1. 2. 3. with 1 unicode symbol 1… and have it serve the exact language function (repetitions)
Is it possible to have Hindley–Milner type system for a LISP?
Hackett. But the language is now abandoned.
I don't know what Hindley-Milner means, but maybe Coalton matches what you mean? <https://github.com/coalton-lang/coalton>
It's static typing with inference. Essentially what you have in Haskell/OCaml/F# where you declare a variable `x` through a let-binding witout specifying its type (`let x = something`), and the compiler analyses `something` and infers the type of `x`.
See Typed Racket https://docs.racket-lang.org/plait/Type_Checking_and_Inferen...
Typed Racket doesn't use H-M.
See also https://github.com/mflatt/shplait, which is a language with (1) the HM type system and (2) the same syntax that Rhombus uses. The language itself is implemented in Rhombus.
Absolutely: you can make that with some macros and using type annotations. I do not know how difficult can it be, but surely there are thing already done.
This is good to see, but I think too much effort is being used to maintain the prefix style function application. As a result a significant amount of the novelty is just mapping syntax from new to old. Special cases, special cases everywhere, even in an implementation like this which is specifically attempting to allow you to make your own "special" cases.
Is it statically typed, like Ocaml?
Is this a reincarnation of Pyret?
No.