by DonHopkins | 2025-08-01 07:57:46+00:00
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:

my $tree = { name => "root", children => [ { name => "leaf" } ] };

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.