Comment by munificent
2 days ago
I think Perl died from a combination of three factors:
1. The rise of other web languages that did what Perl was good at but better.
Perl was probably a fine language for sysadmins doing some text munging. Then it became one of the first languages of the web thanks to Apache and mod_perl, but it was arguably never great at that. Once PHP, Python, Ruby, and (eventually) JavaScript showed up, Perl's significant deficiencies for large maintainable codebases made it very hard to compete.
In many cases, a new, better language can't outcompete an old entrenched one. The old language has an ecosystem and users really don't like rewriting programs, so that gives it a significant competitive advantage.
But during the early rise of the web, there was so much new code being written that that advantage evoporated. During the dot com boom, there were thousands of startups and millions of lines of brand new code being written. In that rare greenfield environment, newer languages had a more even playing field.
2. Perl 6 leaving its users behind.
As a language maintainer, I feel in my bones how intense the desire is to break with the past and Do Things Right This Time. And I'm maintaining a language (Dart) that is relatively new and wart-free compared to Perl. So I can't entirely blame Wall for treating Perl 6 as a blank check to try out every new idea under the sun.
But the problem is that the more changes you make to the language, the farther you pull away from your users and their programs. If they can't stay with you, you both die. They lose an active maintainer for the core tools they rely on. And you lose all of their labor building and maintaining the ecosystem of packages everyone relies on.
A racecar might go a lot faster if it jettisons all the weight of its fuel tank, but it's not going to faster for long.
I think the Perl 6 / Raku folks go so excited to make a new language that they forgot to bring their users with them. They ran ahead and left them behind.
3. A wildly dynamic language.
If you want to evolve a language and keep the ecosystem with you while you do it, then all of that code needs to be constantly migrated to the new language's syntax and semantics. Hand-migrating is nightmarishly costly. Look at Python 3.
It's much more tractable if you can do most of that migration automatically using tools. In order to do that, the tools need to be able to reason in detail about the semantics of a program just using static analysis. You can't rely on dynamic analysis (i.e. running the code and seeing what it does) because it's just not thorough enough to be safe to rely on for large-scale changes to source code.
Obviously, static types help a lot there. Perl 5 not only doesn't have those, but you can't even parse a Perl program without running Perl code. It is a fiendishly hard language to statically analyze and understand the semantics of.
So even if the Perl 6 folks wanted to bring the Perl 5 ecosystem with them, doing so would have been extremely challenging.
I would say this is a case study in a programming language tragedy, but I honestly don't even know if it's a bad thing. It may be that programming languages should have a life cycle that ends in them eventually being replaced entirely by different languages. Perhaps Perl's time had simply come.
I am grateful for all of the innovative work folks have done on Perl 6 and Raku. It's a cornucopia of interesting programming language ideas that other languages will be nibbling on for decades.
Re #1:
Perl did not only have mod_perl. It also had the same kind of frameworks that made Ruby and Python great for web development. It was called Catalyst and was production ready around the same time as RoR and Django.
The real reason why perl failed on this front, IMHO, is that the language makes it super unergonomical to define any nested data structures. In Javascript, Ruby and Python, a list of dictionaries is just some JSON-like syntax: { "x": [...], "y": [...] }
In perl you have to deal with scalars, references, references to scalar, value references, ... and you have the sigils that mean different things depending on what the variable contains. I mean, I spent significant time writing perl and never figured this out.
In a world where you just want a CRUD to load/save a piece of structured data, the ones that let you operate on the data and keep your sanity wins.
Yeah, the syntax being “lexically typed” by the sigils while the language not being statically type-checked, was a bit the worst of both worlds.
Catalyst, Dancer, and Mojolicious actually all exist and have for some time. All of them support PSGI.
Mason was available before any of those.
There are also other, less popular options as is often the case with any Perl feature or module.
I remember encountering exactly that problem in the late 80's when trying to write down a Perl program that I mentally designed by foolishly thinking in terms of Lisp / PostScript (later Python / JavaScript / Ruby) polymorphic arrays with normal object references.
I had a clear simple straightforward mental model of how Lisp and PostScript references and also C pointers worked, which only misled and confused and disappointed me when thinking about Perl:
https://donhopkins.medium.com/the-shape-of-psiber-space-octo...
When I sat down and tried to type my simple straightforward design in as Perl, I suddenly realized that you can't have a reference to something that doesn't have a name, and that Perl name based references, as much as they masqueraded as C or C++ "&" syntax that takes the address of an object, were a TOTALLY DIFFERENT THING.
Perl loved to cherry pick and amalgamate the syntax of other languages (usually terrible languages like bash and awk), presumably to "help people who know that language learn Perl easily", but without any of the semantics, which actually makes it MUCH harder to learn, because "&" LOOKS like C but behaves completely differently and is much more limited and quirky.
You don't have to assign a C value to a global variable in order to take its address.
The syntax and choice of sigils isn't the hard part about learning a language, and if you get the semantics wrong, imitating just the shallow surface syntax of another language that got the semantics right only confuses and disappoints.
When you haphazardly mix and match the "syntactic sugar" of many poorly designed languages together into a discordant stew of artificial sweeteners with bizarrely different semantics, you get "syntactic syrup of ipecac".
Pre-Perl 5 (Perl 4 and earlier):
References were purely symbolic, built on typeglobs and package-variable names. You could only alias or refer to a variable that lived in the symbol table; there was no way to grab a pointer to a literal or temporary value, and you couldn't take a reference to a lexical (my) variable under strict. This made nested or nameless data structures effectively impossible without heavy manual bookkeeping.
Perl 5.0 (October 1994) — Hard references and anonymous structures:
Perl 5 finally introduced true, first-class references via the backslash operator (\) and the anonymous data constructors: [ … ] for arrays and { ... } for hashes. This let you write things like:
without ever giving the inner hash or array a global name.
Explicit dereferencing, context-sensitivity, and autovivification:
Even with hard references, you must unpack them explicitly ($ref->[0], $href->{key}), and Perl’s context rules (scalar vs. list vs. void) can still be tricky. Perl’s autovivification feature (auto-creation of nested hashes/arrays on first dereference) eases some boilerplate—but can also surprise newcomers used to more explicit models.
How Perl compares to Lisp, PostScript, JavaScript, Ruby, Python, etc:
Lisp: Lists are built from cons cells and can be manipulated directly as first-class values, e.g. (list 1 2 (list 3 4)).
PostScript: Arrays ([1 2]) and dictionaries (<< /Key (Value) >>) are literal, nested, and don’t require separate pointer syntax.
JavaScript / Ruby / Python: All provide JSON-style literals ([1,2,[3,4]], {"x":1,"y":[2,3]}) and let you dive into nested structures without ever writing a backslash or sigil beyond the top-level container.
By not getting references right from day 1, then reluctantly iterating over decades on experimental hacks and after-the-fact syntactic baggage layered on top of the initial terrible design, Perl made trade-offs that cost much more than they saved from the self-inflicted problems they were trying to solve.
Fragmented syntax: Multiple dereferencing forms and feature-gated tweaks (postfix deref, autovivification pragmas) splinter Perl into a patchwork of behaviors.
Surprise side-effects: Autovivification can silently spawn large, unused data structures, leading to hidden memory/performance hits.
Subtle bugs: Context-sensitive rules (when autovivify, when not) remain a common gotcha, even for veteran Perl hackers.
Compatibility drag: Supporting both legacy 5.x versions and modern features forces extra version-checks, boilerplate, and warning squelches.
Cognitive overhead: Juggling backslashes, sigils, and varied deref styles demands a more fragmented mental model than the uniform list/map syntax of Lisp-style or JSON-based languages.
"The unspoken, right brain constraint here is that the complexity introduced by a solution to a design problem must be somehow proportional to the problem's importance." -Guido van Rossum
Edit: Of course Perl finally apes JSON-style literal syntax almost verbatim, but still has the old bad semantics, and getting there plastered over deep complexity you still have to cope with:
Feature-gated syntax: New deref forms (postfix, slices) require pragmas or newer Perl versions.
Autovivification surprises: Nested lookups can silently create unused data, bloating memory.
Multiple deref styles: Backslashes, braces, arrows and sigils—each with its own context rules—fracture your mental model.
Legacy support: To run on <5.24, you need version checks, warning silences, or to avoid modern sugar altogether.
Hidden complexity: Under the JSON-like literals lies a maze of sigils, contexts, and edge-case behaviors—far from the uniform simplicity of Lisp / PostScript / JS.
"Many, if not most" isn't good enough, because the edge cases always come back to bite you. You can't just rearrange the deck chairs on the Titanic and expect the ship not to sink. And sank it did, a long time ago.
> JavaScript / Ruby / Python: All provide JSON-style literals ([1,2,[3,4]], {"x":1,"y":[2,3]})
The Perl syntax for this is pretty similar:
[1,2,[3,4]], {"x", 1,"y", [2,3]}
that can also be written, with a bit of syntactic sugar:
[1,2,[3,4]], {x => 1, y => [2,3]}
For many, if not most, cases, given a Perl data structure, the round trip "Perl -> JSON -> Perl" is transparent.
> I suddenly realized that you can't have a reference to something that doesn't have a name
This is untrue though.
1 reply →
> But the problem is that the more changes you make to the language, the farther you pull away from your users and their programs. If they can't stay with you, you both die.
Should people just not make new languages any more?
Should new languages not bear deep resemblances to existing ones?
> Hand-migrating is nightmarishly costly. Look at Python 3.
> It's much more tractable if you can do most of that migration automatically using tools.
There was and is in fact abundant tool support for this in Python. The `2to3` script shipped with Python and the corresponding `lib2to3` was part of the standard library through 3.11. The third-party `six` compatibility libraries are still downloaded from PyPI more often than NumPy.
> In order to do that, the tools need to be able to reason in detail about the semantics of a program just using static analysis.
The experience of these tools users disagrees, from what I've seen. They produce ugly code in places, but it generally passes the tests. Yes, manual fixes are often necessary, but there were source packages published in that era (and it's not like they disappeared from PyPI either) that would detect the Python version in `setup.py`, run the tool if necessary at installation time, and have happy users.
> Should people just not make new languages any more?
They should, but they should understand it's very hard to get widespread adoption when they do. Not impossible, but very hard.
> There was and is in fact abundant tool support for this in Python. The `2to3` script shipped with Python and the corresponding `lib2to3` was part of the standard library through 3.11. The third-party `six` compatibility libraries are still downloaded from PyPI more often than NumPy.
Yes, and even so everyone involved knows the transition took a very long time, required a ton of labor, and was a major headache for many years.
Work on Python 3 began in 2006, and Python 2 wasn't officially sunsetted until 2020 [1]. To put that in perspective, the Python community spend almost as much time migrating to Python 3 as they spent in Python's entire history prior to that point. (Python was 15 in 2006, and it took them 14 years to deprecate Python 2.)
You'll notice too that the easiest changes to automate were the ones that were either lexical (renaming core library methods) or syntactic (print to print()), and the hardest ones involved the change to types of values (strings).
Static types make it much easier to automatically migrate code.
[1]: https://www.python.org/doc/sunset-python-2/
Python's soul is "Pythonic", which stayed pure from Python 2 to Python 3, but Perl's soul was "Pearl-ick", and Perl 6 had a totally different kind of "ick" than Perl 5 did.
Perl's language design fetishizes solving self-inflicted puzzle after puzzle after puzzle.
https://www.artima.com/weblogs/viewpost.jsp?thread=147358
Language Design Is Not Just Solving Puzzles
by Guido van Rossum, February 10, 2006
Summary: An incident on python-dev today made me appreciate (again) that there's more to language design than puzzle-solving. A ramble on the nature of Pythonicity, culminating in a comparison of language design to user interface design.
Some people seem to think that language design is just like solving a puzzle. Given a set of requirements they systematically search the solution space for a match, and when they find one, they claim to have the perfect language feature, as if they've solved a Sudoku puzzle. For example, today someone claimed to have solved the problem of the multi-statement lambda.
But such solutions often lack "Pythonicity" -- that elusive trait of a good Python feature. It's impossible to express Pythonicity as a hard constraint. Even the Zen of Python doesn't translate into a simple test of Pythonicity.
In the example above, it's easy to find the Achilles heel of the proposed solution: the double colon, while indeed syntactically unambiguous (one of the "puzzle constraints"), is completely arbitrary and doesn't resemble anything else in Python. A double colon occurs in one other place, but there it's part of the slice syntax, where a[::] is simply a degenerate case of the extended slice notation a[start:stop:step] with start, stop and step all omitted. But that's not analogous at all to the proposal's lambda <args>::<suite>. There's also no analogy to the use of :: in other languages -- in C++ (and Perl) it's a scoping operator.
And still that's not why I rejected this proposal. If the double colon is unpythonic, perhaps a solution could be found that uses a single colon and is still backwards compatible (the other big constraint looming big for Pythonic Puzzle solvers). I actually have one in mind: if there's text after the colon, it's a backwards-compatible expression lambda; if there's a newline, it's a multi-line lambda; the rest of the proposal can remain unchanged. Presto, QED, voila, etcetera.
But I'm rejecting that too, because in the end (and this is where I admit to unintentionally misleading the submitter) I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.
And I like it that way! In a sense, the reason I went to considerable length describing the problems of embedding an indented block in an expression (thereby accidentally laying the bait) was that I wanted to convey the sense that the problem was unsolvable. I should have known my geek audience better and expected someone to solve it. :-)
The unspoken, right brain constraint here is that the complexity introduced by a solution to a design problem must be somehow proportional to the problem's importance. In my mind, the inability of lambda to contain a print statement or a while-loop etc. is only a minor flaw; after all instead of a lambda you can just use a named function nested in the current scope.
But the complexity of any proposed solution for this puzzle is immense, to me: it requires the parser (or more precisely, the lexer) to be able to switch back and forth between indent-sensitive and indent-insensitive modes, keeping a stack of previous modes and indentation level. Technically that can all be solved (there's already a stack of indentation levels that could be generalized). But none of that takes away my gut feeling that it is all an elaborate Rube Goldberg contraption.
Mathematicians don't mind these -- a proof is a proof is a proof, no matter whether it contains 2 or 2000 steps, or requires an infinite-dimensional space to prove something about integers. Sometimes, the software equivalent is acceptable as well, based on the theory that the end justifies the means. Some of Google's amazing accomplishments have this nature inside, even though we do our very best to make it appear simple.
And there's the rub: there's no way to make a Rube Goldberg language feature appear simple. Features of a programming language, whether syntactic or semantic, are all part of the language's user interface. And a user interface can handle only so much complexity or it becomes unusable. This is also the reason why Python will never have continuations, and even why I'm uninterested in optimizing tail recursion. But that's for another installment.