QIAOPANG.COM · SELECTED ESSAYS (2013–2026) · 260 ESSAYSBrowse Chronologically (2013 → 2026) ↗
QiaoPang · Programmers LifeSelected Essays · Systems, Craft & Agentic Era
Timeline ↗

The Disruptor's Game: Programming Languages

The Disruptor's Game: Programming Languages

The users of programming languages are programmers, and their demands on a language boil down to: usable, bug-free, fast to develop with, easy to write high-quality code in, performant, scalable, easy to deploy, with a complete ecosystem, blablabla. So the value proposition of this vast programming-language market revolves mainly around these needs. This market is different from the winner-take-all internet markets: the leading player holds onto no more than 20% of the head, and the long tail stretches out hundreds of names deep.

Originally, programming languages built their value propositions around performance, and around usability at that same order of magnitude of performance. C, tightly bound to the hardware, was the biggest winner. Every language on the market fixed its eyes on C, and the direction of compiler development was performance, performance, performance.

In the mid-90s, Java — by a twist of fate — grew up amid the internet wave and became a disruptor. Initially positioned for home appliances, Java would have died many deaths over if it hadn’t caught the first great wave of the internet. Java’s value proposition was “write once, run everywhere.” I won’t duke it out with you on performance, and I won’t duke it out with you on ecosystem; I have the JVM, so I can do what you can’t. Virtual machines for languages had existed for ages — but moving them into the realm of mainstream development, Java was probably the first.

Early Java was astonishingly slow, its ecosystem pitifully small, and even its value proposition was empty rhetoric (how do you run Java bytecode where there’s no JVM?). This was precisely what its competitors sneered at. Yet none of it stopped Java from steadily accumulating a massive base of early adopters on the strength of its distinctive traits. At the same time, Sun was flush with cash back then and, holding fast to its conviction of contending with Microsoft for supremacy, dreamed of using the JVM to insulate Java from the OS and pull the rug out from under Windows. So it threw nearly everything behind Java, and with the full support of IBM — a fellow victim of Microsoft’s bullying — Java finally crushed every contemporary language, both in VM and in ecosystem (J2EE & all manner of open-source software).

Twenty years on, Java/JVM is now the benchmark for performance. Who still mocks Java’s efficiency?

After Java came dotnet, taking an evolved version of the VM as its cornerstone and the CLR as its distinctive value proposition — write in whatever language you love, and dotnet will let it run — to challenge Java. It had a real shot at winning, but alas, Microsoft didn’t build its ecosystem in an open-source posture from day one, so it could only ever be a tyrant in its own den, roughing up the already fading Delphi on the Windows platform.

Since the CLR counted as sustaining innovation — its ideas built on bytecode — it was easy for the JVM to take those ideas and make them its own. And so something bizarre happened: on the CLR, apart from Microsoft’s own C#, F#, and VB.net, there were seemingly only IronPython and IronRuby to make up the numbers; whereas on the JVM, a hundred flowers bloomed — it not only kept absorbing old languages but gave birth to many new ones, like Scala, made famous by Twitter, and Clojure, a Lisp variant. dotnet’s value proposition was turned, in reverse, into yet another ace up the JVM’s sleeve.

The bloody facts tell everyone: you can’t beat an apple with a better apple; you need an orange.

As the internet revolution deepened, browser/server software development came to prize the small, fast, and nimble ever more. While your side is still charging up the mighty Toad Style, the other side has already run through all eighty-one moves of the Evil-Warding Sword Style. A writer on Medium complained, bitterly and vividly: “Writing Java code is like performing a whole ritual before making love — so much ceremony and red tape…”

By the time Moore’s Law rolled into the twenty-first century, performance had, in many computing domains, taken a back seat — fast development was king. Hence the counterattack of dynamically typed languages against statically typed ones. This is the inevitable rebound after languages grow ever more complicated as they evolve — nowadays, any object-oriented language that supports templates, type inference or not, has its code torn between abstraction and generalization for a while, until it feels like you’re trapped in the six realms of reincarnation, hopelessly complex.

So first came PHP, which counterattacked in web development with minimalism (though PHP is no longer simple, of course).

Then Python’s fresh, clean style began to conquer the tooling market, and the market for programs whose target users aren’t programmers (scripts, plotting, scientific computing…).

After that, Rails burst onto the scene on the strength of Ruby’s powerful DSL capabilities and threw the web-app market into chaos. In an instant, a whole series of traditional web frameworks — asp.net (dotnet mvc), jsp (ssh) — were utterly outclassed. Rails’ magic suddenly placed web developers, who had been living in the Dark Ages, in the beautiful era of the Renaissance. What used to take five or six Java programmers grinding away miserably could now be wrapped up single-handedly by one cheerful Rails engineer. Spurred on by Rails, all manner of high-quality weekend projects sprang up like bamboo shoots after rain. It’s hard to say whether Rails prospered web 2.0 or web 2.0 made Rails.

Next came node.js. Its value proposition was also usability, but it took a different path — frontend/backend unified development (other things like async I/O and event-driven design were not its killer feature). In just a few short years, node.js bred a massive ecosystem. Frontend engineers began to hold their heads high and play at full stack, and so they all became very mean footnote:[Just kidding — “mean” actually stands for MongoDB, Express, Angular, Nginx].

Performance, cross-platform — by the web-app era, these value propositions were being replaced by usability, fast development, and high-quality code. Dynamic typing grabbed a good deal of territory this round.

Times change. Upon entering the cloud-computing era, functional languages began to take on imperative languages. With Moore’s Law essentially played out, software could only move from scaling up to scaling out — precisely where functional programming languages shine. The programming-language market was calling for a language or VM that could write code with concurrency primitives as its instruction base and divide-and-conquer as its guiding principle. And so, all the functional languages that had existed for ten or even scores of years emerged to forage like brown bears just out of hibernation.

Just as the x86’s scarcity of registers forced engineers to optimize stack operations to the extreme in hopes of speeding things up at the function-call level — and inadvertently cultivated a peerless push/pop martial art — functional languages, built on recursion and pattern matching, have optimized these two arts to perfection. “Recursion is inefficient”? No, no, no — that’s already a huge cognitive error.

Among the functional languages of the cloud era, the one that has gone furthest is surely Erlang/BEAM (Erlang’s VM). Erlang takes functional programming as its cornerstone and makes concurrency and distribution among its core design principles — hitting exactly the value proposition the cloud-computing era expects. BEAM’s great talismans are: a soft-realtime process scheduler, a message dispatching system, and a pattern-matching engine. In just a few hundred lines of code, you can write a distributed KV store (a beggar’s version of Riak or Redis) that runs on BEAM, applies consistent hashing, scales out to any number of servers, and has extremely high fault tolerance.

As disruptors, functional languages have only just arisen, but riding the trend of the times, their influence has already expanded rapidly.

The final disruptor is Lisp. Lisp can be considered the progenitor of programming languages, forever the object of study for all others. Paul Graham once said with disdain: “The current development of programming languages has only just caught up to the level of Lisp in 1958” (see Ruan Yifeng’s translation of Hackers & Painters).

Among Lisp’s many ideas, “code is data, data is code” is the most destructive. Through quote and unquote, shuttling and maneuvering across the syntax tree of the code, you are practically writing programs from God’s-eye view. dotnet’s LINQ is very sexy, but any language derived from Lisp can define its own macros and thereby trivially write code like this:

query = from w in Weather,
          where: w.prcp > 0 or is_nil(w.prcp),
          select: w

In the world of Lisp, a language’s vitality is no longer constrained by its syntax; much of the work originally done by class libraries at runtime is hoisted by macros to compile time.

Lisp’s magic, meeting all manner of VM ecosystems, gives rise to all kinds of intoxicatingly powerful languages. Let’s set aside Clojure on the JVM and look at Elixir on BEAM. As mentioned, one of BEAM’s talismans is pattern matching — and now pattern matching + macros + hot code reload (another BEAM feature) form an outrageous combination, and all at once the code we used to write is utterly outclassed.

Just to give an example. Suppose I’m building a shopping mall, with two tables in the database: a product table and a category table. To display a product, I need to join the two tables. Since product categories cap out at a scale of tens of thousands or hundreds of thousands at most, and adding new categories is an infrequent operation, I could simply write a macro that reads from the database the mapping between existing category IDs and their content, generating tens of thousands of pattern-matching statements (or functions), and only falling back to reading the database when nothing matches. The few lines of macro I write expand, after compilation, into tens or hundreds of thousands of lines of code, executed by the pattern-match engine in the VM at efficiency far surpassing any third-party cache, let alone a database join. And once new categories accumulate to a certain number, I just recompile the relevant module’s code and hot-reload it, and the new categories instantly become part of the running code.

This is no alarmist theory but real industry practice. Elixir’s own Unicode case-conversion module works on precisely this idea. Currently, the languages that can perfectly handle case conversion beyond English, whether at the language level or the library level, can be counted on one hand — try it if you don’t believe me (e.g., in Python):

In [3]: print string.upper("é")
é

Ugh! It did nothing! This is because, across different natural languages, the mappings among various letter cases number in the tens of thousands — far more than the 26 letters can cover — so most programming languages simply give up when they hit this and leave it to the developer to figure out. Elixir’s approach is the same as the shopping-mall example above, brutally outrageous: it opens a unicode.txt containing all the case mappings for every character, reads the information in it, and at compile time generates tens of thousands of functions like this:

defp do_upcase("é" <> rest) do
  ["É"] ++ upcase(rest)
end

defp do_upcase("ë" <> rest) do
  ["Ë"] ++ upcase(rest)
end

And then exposes a upcase interface to the user. As a programmer of sound mind, you would never write code this way — and if you did, you’d be fired by your boss the next day. Think about it: if you used such an upcase function to process a French edition of Twenty Thousand Leagues Under the Sea, that means millions of characters to handle, that is, millions of pattern matches and millions of recursive calls to different functions — how horrible would that efficiency be?

➜  scripts  time elixir up.exs
elixir up.exs  0.36s user 0.11s system 117% cpu 0.393 total

0.36s! Let’s try doing the same with Python’s string.upper — except line by line rather than character by character:

➜  scripts  time python up.py
python up.py  0.03s user 0.01s system 93% cpu 0.048 total

Wow, the Python code is ten times more efficient than the Elixir code (after all, Python uppercases line by line, with only about 1/100 the number of function calls as Elixir). But the Python code’s problem is that it doesn’t correctly convert case — all non-English letters are simply left unrecognized. If you wanted to make this Python code correct, the most straightforward way is to set up a table like this:

convertion = {
  u"é": u"É",
  u"ë": u"Ë",
  ...
}

And then look up each Unicode character you read, converting it via the table, and falling back to the system’s upper function if it’s not in the table. How efficient do you think such code would be?

➜  scripts  time python up_with_dict.py
python up_with_dict.py  50.82s user 1.96s system 99% cpu 52.856 total

50.82s! Nearly 300 times slower! Of course, for the program above, you could optimize the algorithm in all sorts of ways, or even switch to C, and perhaps eventually approach the level Elixir can reach — but the complexity of the code would have entered another world entirely.

This is the disruption Lisp brings: a whole new system of thought.

The disruptor’s game is a game that never ends. Today’s winners may be abandoned tomorrow. A disruptor must have a unique, hard-to-replicate advantage in its value proposition, rise from the low end of the market, and slowly — in ways the industry leaders never anticipate — gnaw away one niche market after another. Every programming language has its own distinctive DNA — C/C++ can’t learn to be Java, just as the JVM can’t become BEAM, and just as Scala/Akka must bow their proud heads before Erlang.

I accidentally wrote such a long piece — thank you for reading! The fact that you’ve read this far shows you’re a serious programmer.

MORE IN Languages & Craft

Further Reading in This Column

View all essays in “Languages & Craft” →

Time to Meet jj: Git's Next Evolutionary Step

Git has ruled version control for twenty years, but its staging area, brittle rebases, and blocking merge conflicts are becoming liabilities in the AI era. jj rebuilds the mental model from the ground up — while staying fully Git-compatible. Here's why it deserves your attention.

New Hope (2025–2026)5,588 chars · 12 min read