The Programming Languages Zoo
Posted2 months agoActive2 months ago
plzoo.andrej.comTechstoryHigh profile
calmpositive
Debate
40/100
Programming LanguagesLanguage DesignType Systems
Key topics
Programming Languages
Language Design
Type Systems
The Programming Languages Zoo is a collection of concise implementations of various programming languages, sparking discussions on language design, features, and the possibility of a language with all possible features.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
5d
Peak period
46
120-132h
Avg / period
18.3
Comment distribution55 data points
Loading chart...
Based on 55 loaded comments
Key moments
- 01Story posted
Oct 23, 2025 at 2:01 AM EDT
2 months ago
Step 01 - 02First comment
Oct 28, 2025 at 7:37 AM EDT
5d after posting
Step 02 - 03Peak activity
46 comments in 120-132h
Hottest window of the conversation
Step 03 - 04Latest activity
Oct 29, 2025 at 2:46 AM EDT
2 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
ID: 45678632Type: storyLast synced: 11/20/2025, 1:48:02 PM
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
Is it possible to create a programming language that has every possible feature all at once?
I realize there are many features that are opposed to each other. Is it possible to “simply” set a flag at compile / runtime and otherwise support everything? How big would the language’s source code be?
Personally, I just prefer to know that the team that supports the tool I'm using is dedicated to "that one thing" I'm after.
ARC stands for Automatic Reference Counting. The compiler manages the reference counts.
(Although the compiler inserts ref count inc and dec calls automatically, so ...)
It could be done... but I'm not sure what it would gain you. It seems to me that knowing that it will do one or the other is better than having to think about which it will do in each instance.
"In practice, the challenge of programming language design is not one of expanding a well-defined frontier, it is grappling with a neverending list of fundamental tradeoffs between mutually incompatible features.
Subtyping is tantalizingly useful but makes complete type inference incredibly difficult (and in general, provably impossible). Structural typing drastically reduces the burden of assigning a name to every uninteresting intermediate form but more or less precludes taking advantage of Haskell-style typeclasses or Rust-style traits. Dynamic dispatch substantially assists decoupling of software components but can come with a significant performance cost without a JIT. Just-in-time compilation can use runtime information to optimize code in ways that permit more flexible coding patterns, but JIT performance can be unpredictable, and the overhead of an optimizing JIT is substantial. Sophisticated metaprogramming systems can radically cut down on boilerplate and improve program concision and even readability, but they can have substantial runtime or compile-time overhead and tend to thwart automated tooling. The list goes on and on."
From https://lexi-lambda.github.io/blog/2025/05/29/a-break-from-p...
Her first example is excellent. In Haskell, we have global type inference, but we've found it to be impractical. So, by far the best practice is not to use it; at the very least, all top-level items should have type annotations.
the second one, structural typing: have your language support both structural types and nominal types, then? This is basically analogous to how haskell solved this problem: add type roles. Nominal types can't convert to one another, whereas structural types can. Not that Haskell is the paragon of language-well-designed-ness, but... There might be some other part of this I'm missing, but given the obviousness of this solution, and the fact that I haven't seen it mentioned, it is just striking.
on dynamic dispatch: allow it to be customized by the user - this is done today in many cases! Problem solved. Plus with a global optimizing compiler, if you can deal with big executable size, you have cake and eat cake.
on JIT: Yes, JIT can take some time, it is not free. JIT can make sense even in languages that are AOT compiled, in general it optimizes code based upon use patterns. If AOT loop unrolling makes sense in C, then I certainly think runtime optimization of fully AOT compiled code must be advantageous too. But, today, you can just about always figure that you can get yourself a core to do this kind of thing on, we just have so many of them available and don't have the tools to easily saturate them. Or, even if you do today with N cores, you probably won't be able to on the next gen, when you have N+M cores. Sure, there's gonna have to be some overhead when switching out the code, but I really don't think that's where the mentioned overhead comes from.
Metaprogramming systems are another great example: Yes, if we keep them the way they are today, at the _very least_ we're saying that we need some kind of LSP availability to make them reasonable for tooling to interact with. Except, guess what, all languages nowadays of any reasonable community size will need LSP. Beyond that, there are lots of other ways to think about metaprogramming other than just the macros we commonly have today.
I get her feeling, balancing all of this is hard. One think you can't really get away from here is that all of this increases language, compiler, and runtime complexity, which makes things much harder to do.
But I think that's the real tradeoff here: implementation complexity. The more you address these tradeoffs, the more complexity you add to your system, and the harder the whole thing is to think about and work on. The more constructs you add to the semantics of your language, the more difficult it is to prove the things you want about its semantics.
But, that's the whole job, I guess? I think we're way beyond the point where a tiny compiler can pick a new set of these tradeoffs and make a splash in the ecosystem.
Would love to have someone tell me how I'm wrong here.
I also highly recommend the book: https://en.wikipedia.org/wiki/Concepts,_Techniques,_and_Mode...
I for one would LOVE to make semicolons; optional and choose between {} braces/indentation per project/file, just like we can with tabs/spaces now (tabs are superior)
I'm not sure that this is the case.
That said, for the first thing they asked for (different syntactical views on the same "substrate," where I'm assuming the language has one model of how its runtime works), that's very doable.
Is this a universal concept?
In common languages, you're usually still targeting the same runtime in different compilation units, but it's a rough description of optimization boundaries (you compile one unit at a time and stitch them together during linking). Some techniques bridge the gap and thus the language crispness (e.g., post-hoc transformations on compiled units, leading to one larger "effective" compilation unit), but you can roughly think of it as equivalent to a whole shared library.
Some random thoughts about this:
If languages are a tool of communication between programmers (there's that adage "primarily for humans to read, and only secondarily for computers to run") would this be a good idea?
Wouldn't each set of flags effectively define a different language? With a combinatorial explosion of flags.
An act of design is not only about what to include, but what to leave out. Some features do not interact well with others, which is why tradeoffs exist. You'd have to enforce restrictions on which flags can be used together.
You'd be designing a family of programming languages rather than a single language. Finding code in this language would tell you little, until you understood the specific flags.
Seriously tho, not really because programming languages are designed, and design is all about carefully balancing tradeoffs. It starts with the purpose of the language -- what's it for and who will be using it? From there, features follow function. Having all the features is counterproductive because some features are only good in the absence of others, others are mutually exclusive. For example, being 1-indexed or 0-indexed. You can't be both, unless it's configurable (and that just causes bugs as Julia found out).
If you want your language to be infinitely configurable to meet the needs of everyone, then you would want it to be as small as possible, not big. Something like Lisp.
However, can a lot more "features", or better, "programming paradigms" or as I would call them "architectural styles" be accommodated together, usefully?
Absolutely yes!
Objective-S does this using a combination of two basic techniques:
1. A language defined by a metaobject protocol
2. This metaobject protocol being organized on software architectural principles
(1) makes it possible to create a variable programming language. (2) widens the scope of that variability to encompass pretty much all of programming.
What was surprising is how little actual language mechanism was required to make this happen, and how far that little bit of mechanism goes. Eye-opening!
Site for the language:
https://objective.st
However, the site doesn't really have the explanation of how 1+2 work and work together. That is explained as best I could manage within the space limitation of an academic paper in Beyond Procedure Calls as Component Glue: Connectors Deserve Metaclass Status.
https://2024.splashcon.org/details/splash-2024-Onward-papers...
ACM Digital Library (open access):
https://dl.acm.org/doi/10.1145/3689492.3690052
Alas, the PhD thesis that goes into more detail is still in the works (getting close, though!). That still being in the works also means that the site will not be updated with that information for a bit. Priorities...
Serious (germ of a) question.
A bad idea, probably. LLM output needs to be reviewed very carefully; optimizing the language away from human review would probably make the process more expensive. Also, where would the training data in such a language come from?
In the end, we circle back to lisps, once you're used to it, it's as easy for humans to parse as it is for machines to parse it. Shame LLMs struggle with special characters.
> shame LLMs struggle
That sounds like Stockholm syndrome more than an easy-to-use language.
edit: Lisp -> s-expressions
But don't take my word for it, ask the programmers around you for the ones who've been looking into lisps and ask them how they feel about it.
Also feels like making sure the tokeniser has distinct tokens for left/right parens would be all that is required to make LLMs work with them
But I'm having way more "unbalanced parenthesis" errors than with Algol-like languages. Not sure if it's because of lack of training data, post-training or just needing special tokens in the tokenizer, but there is a notable difference today.
- memory safe, thread safe, concurrency safe, cancellation safe
- locality of reasoning, no spooky action at a distance
- tests as a first class feature of the language
- a quality standard library / reliable dependency ecosystem
- can compile, type check, lint, run tests, in a single command
- can reliably mock everything, either with effects or something else, such that again we maintain locality of reasoning
The old saying that a complex system that works is made up of simple systems that work applies. A language where you can get the small systems working, tested, and then built upon. Because all of these things with towards minimising the context needed to iterate, and reducing the feedback loop of iteration.
"UI" issues, while not at all unimportant, are surface level. Semantics are fundamental. Get the semantics of your language wrong, and you are stuck. Indeed, this is what happens with most popular languages that aren't well-considered:
- lang author creates some random new lang, throws whatever feels like a good idea to them into the lang
- lang community discover that these features interact in rather surprising ways, ways that keep tripping up newcomers to the language, and would like to change it.
- lang author, having had X more years of experience now, identifies that changing the behavior could break user programs who rely on that behavior (wittingly or un-)
- lang author decides to write a new, backwards-incompatible version, community has 20 years of turmoil before recovering (python3), if it ever does (perl6).
UI is still important, but I do not think it really defines a language.
Let’s say I created a new Haskell compiler, and it had the best error messages in the world, a hiccup-free LSP experience, maybe an alternative syntax format that’s more familiar to mainstream devs. But it still has all the same semantics. Is that still Haskell? Why or why not?
Now let’s say I took whatever has the best tooling currently, idk what it would be, maybe TypeScript, but gave it say non-strict semantics. Is that still typescript?
If you take it to an extreme, like with Smalltalk, you wind up with languages whose environment is considered essential, they are basically part of the language at that point.
To most people it is about IDE and development tool chains.
Or we can think of it from the perspective of mapping programmer’s mental model to the problems they want to solve. Interface to the Mind, so to speak.
I have been intentionally coding with Claude in the past few months and I started to think about programming from problem solving standpoint other than generating artifacts that conform to whatever languages, libraries and frameworks I happen to use to solve the problem at the time.
We can call this mental model or not. But it seems to be a different abstraction than we are talking about at PL level.
To me, LLM is part of the interface between me and the solution, that is what I mean by UI.
The real work is still in my head. PL language is just a medium.
Syntax is a UI problem, but programming languages are more than syntax sugar for assembly. Features like Rust's borrow-checker, type systems, even small things like name resolution and async/await provide "semi-formal"* guarantees. And LLMs are no substitute for even semi-formal guarantees; people have spent lots of money and effort trying to integrate (non-NN, rule-based) algorithms and formal methods into LLMs, some even writing LLM programming languages, because they may be the key to fixing LLMs' accuracy problem.
* Not formally defined or verified, but thoroughly defined and implemented via reliable algorithms. A true formal specification is completely unambiguous, and a true formally-proven implementation is completely accurate to its specification. A thorough language specification or reference (like https://tc39.es/ecma262/ or https://doc.rust-lang.org/nightly/reference/) is almost unambiguous, and a well-tested compiler or interpreter is almost always accurate, especially compared to LLMs.
Cases where true formal guarantees matter exist but are rare. There are many, many cases where LLMs are too inaccurate but well-tested algorithms are good enough.
Tiny invertebrates are anything but boring (although many bore), but too small to make good showcases.
Worms aren't attractive to anyone who isn't on several government lists.