DEV Community

Cover image for Let's Talk About the Pathologically Eclectic Rubbish Lister
Jean-Luc Martel
Jean-Luc Martel

Posted on

Let's Talk About the Pathologically Eclectic Rubbish Lister

We've explored PHP's questionable life choices, JavaScript's relationship with alternate realities, Python's talent for silently corrupting your data, C# quirks and the .Net identity crisis, and Java's industrial-scale production of factories.

But now we've arrived at the main event.

The granddaddy of weird programming languages.

The Swiss Army chainsaw.

Perl.

Created by Larry Wall in 1987, Perl became one of the defining languages of Unix scripting, system administration, text processing, and the early web.

And it came with a philosophy that was almost aggressively different from the languages that followed.

Python would eventually tell us there should preferably be one obvious way to do something.

Perl had already declared:

"There's More Than One Way To Do It."

Known affectionately as TMTOWTDI, pronounced "Tim Toady."

But here's my favorite bit of Perl history.

The name is commonly expanded as Practical Extraction and Report Language.

But Larry Wall also endorsed another expansion:

Pathologically Eclectic Rubbish Lister.

That's not something invented by Perl's critics.

That's the language's own creator getting in on the joke.

And the more Perl you learn, the more you appreciate just how accurate that description can be.

There is so much wonderful weirdness in Perl that we're going to need two articles.

Let's begin with six of its greatest hits.

1. Your Code Doesn't Decide What It Returns. The Caller Does.

Most programming languages have a fairly straightforward understanding of expressions.

You evaluate something.

It produces a result.

The surrounding code does something with that result.

Perl considers this approach unnecessarily restrictive.

Instead, Perl has the concept of context.

Expressions can behave differently depending on whether they're evaluated in scalar context, list context, or void context.

And we're not merely talking about automatic type conversion.

The same expression can perform a fundamentally different operation depending on what the caller expects.

Consider:

my @animals = ("cat", "dog");

my @result = reverse(@animals);
print "@result\n";
Enter fullscreen mode Exit fullscreen mode

Output:

dog cat
Enter fullscreen mode Exit fullscreen mode

Yawn.

Now change the assignment:

my $result = reverse(@animals);
print "$result\n";
Enter fullscreen mode Exit fullscreen mode

Output:

godtac
Enter fullscreen mode Exit fullscreen mode

Interesting.

We didn't change the function.

We didn't change the arguments.

We changed the type of variable receiving the result.

And Perl completely changed what reverse() does.

In list context, reverse() reverses the order of the elements.

In scalar context, it concatenates the elements and reverses the resulting string.

The first example reverses the list.

The second reverses the characters.

The caller effectively gets to decide which version of reality it wants.

Here's another example:

my @colors = ("red", "green", "blue");

my $count = @colors;

print "$count\n";
Enter fullscreen mode Exit fullscreen mode

Output:

3
Enter fullscreen mode Exit fullscreen mode

When an array is evaluated in scalar context, you get the number of elements.

In list context, you get the elements themselves.

This is all deliberate.

In fact, Perl even provides wantarray, allowing subroutines to detect whether they're being called in list, scalar, or void context.

Meaning a function can explicitly choose what to do based on how its return value will be used.

To be fair, this can be genuinely powerful.

It's also an excellent way to make a function behave differently after someone innocently refactors the calling code.

The lesson: In Perl, context isn't merely where an expression appears. It's part of what the expression means.

2. Zero Is False. Except When Zero Is True.

We've encountered strange truthiness rules before.

PHP, for example, considers the string "0" to be false.

Perl does too. (And no, PHP didn't start as a Perl script, but that's for a different post).

print "TRUE\n" if "0";
Enter fullscreen mode Exit fullscreen mode

Output:

FALSE
Enter fullscreen mode Exit fullscreen mode

So far, nothing we haven't seen before.

But Perl has a particularly entertaining trick.

my $value = "0E0";

print "TRUE\n" if $value;
Enter fullscreen mode Exit fullscreen mode

Output:

TRUE
Enter fullscreen mode Exit fullscreen mode

Okay.

Now:

my $value = "0E0";

print $value + 0;
Enter fullscreen mode Exit fullscreen mode

Output:

0
Enter fullscreen mode Exit fullscreen mode

Let's review.

The value is numerically zero.

But it's logically true.

Perl has successfully invented zero that isn't false.

The explanation is that "0E0" is a nonempty string that isn't exactly "0".

So it's truthy.

But interpreted numerically, it's scientific notation for zero multiplied by ten to the power of zero.

Which is zero.

This isn't just an amusing edge case.

The idiom is used deliberately in Perl's database ecosystem.

For example, DBI can return the string "0E0" from certain successful operations that affect zero rows.

Why?

Because sometimes you need to distinguish between:

  • An operation that failed.
  • An operation that succeeded but affected zero records.

You want the result to be numerically zero while remaining true in a boolean test.

And "0E0" does exactly that.

Perl has turned an apparent inconsistency into a feature.

That's a very Perl thing to do.

There's even a traditional expression for this kind of value:

"Zero but true."

Which sounds less like a programming concept and more like a philosophical position.

The lesson: Perl's scalar values can behave differently in numeric, string, and boolean contexts, sometimes quite deliberately.

3. Checking Whether Something Exists Can Create It

Let's talk about nested data structures.

Suppose you have a hash containing configuration information.

You want to find out whether a deeply nested key exists.

Reasonable enough.

use strict;
use warnings;

my %config;

if (exists $config{user}{preferences}{theme}) {
    print "Theme exists\n";
}
Enter fullscreen mode Exit fullscreen mode

Nothing prints.

Of course not.

The hash is empty.

The key doesn't exist.

End of story.

Except...

use Data::Dumper;

print Dumper(\%config);
Enter fullscreen mode Exit fullscreen mode

Output:

$VAR1 = {
          'user' => {
                      'preferences' => {}
                    }
        };
Enter fullscreen mode Exit fullscreen mode

Excuse me?

We checked whether something existed, and Perl created a bunch of things that didn't exist.

The final theme key still doesn't exist.

But the intermediate structures do.

This behavior is called autovivification.

And it's one of Perl's most adorable features.

When you access nested elements through references, Perl can automatically create intermediate structures when the operation requires them.

This makes constructing complex data structures extraordinarily convenient.

You can write deeply nested assignments without manually initializing every level.

But it creates an interesting consequence.

A lookup that appears harmless can have side effects.

In this example, the existence check caused Perl to create the intermediate hashes required to reach the final key.

So the act of asking whether something exists can change the structure you're examining.

To be precise, not every kind of lookup autovivifies, and the exact context matters.

But the behavior above is real, intentional, and documented in the official Perl FAQ.

Which makes the following statement entirely accurate:

"I checked whether the data existed, and now some of it does."

If you need to test a deeply nested structure without modifying it, check each level carefully or use an appropriate helper.

The lesson: Perl sometimes treats data structures as things that should helpfully assemble themselves while you're looking at them.

4. foreach Doesn't Give You a Variable. It Gives You an Alias.

Here's a familiar-looking loop:

my @numbers = (1, 2, 3);

foreach my $number (@numbers) {
    $number *= 10;
}

print "@numbers\n";
Enter fullscreen mode Exit fullscreen mode

What do you expect?

If you're coming from many other programming languages, you might assume that $number is a local variable containing the current element's value.

So modifying it shouldn't necessarily affect the original array.

Right?

Output:

10 20 30
Enter fullscreen mode Exit fullscreen mode

Nope.

You just modified every element in the original array.

In Perl, the foreach loop variable is an alias to the corresponding element when iterating over an ordinary array.

It isn't simply receiving a copy.

So when you write:

$number *= 10;
Enter fullscreen mode Exit fullscreen mode

You're changing the actual array element.

This is useful when you deliberately want to transform a collection in place.

But it's a potential disaster when you don't realize the aliasing is happening.

And remember our PHP article?

PHP has its own famous by-reference foreach surprise.

Except Perl doesn't need an ampersand to make its loop variable an alias.

It just does it.

Naturally.

The safer approach, when you only want to inspect or compute something, is to avoid mutating the loop variable or explicitly copy it:

foreach my $number (@numbers) {
    my $copy = $number;
    $copy *= 10;

    print "$copy\n";
}
Enter fullscreen mode Exit fullscreen mode

Now you're operating on a separate value.

The lesson: Perl's foreach is powerful precisely because the iteration variable can be an alias. Just don't mistake that power for ordinary value-copying semantics.

5. Perl Has an Operator That Remembers Things

Here's where we start getting into genuinely exotic territory.

Consider the range operator:

my @numbers = (1 .. 5);
Enter fullscreen mode Exit fullscreen mode

Pretty straightforward.

You get the integers from 1 through 5.

Nothing especially strange about that.

But remember our first example?

Perl has context.

And context can change what an operator means.

In list context, .. creates a range.

In scalar context, it becomes something entirely different.

A flip-flop operator.

Yes.

An operator that maintains internal state.

Consider:

my @lines = (
    "before",
    "START",
    "alpha",
    "beta",
    "END",
    "after"
);

foreach my $line (@lines) {
    if (($line eq "START") .. ($line eq "END")) {
        print "$line\n";
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

START
alpha
beta
END
Enter fullscreen mode Exit fullscreen mode

What happened?

The scalar .. operator acts like a stateful switch.

Initially, it evaluates to false.

When the left condition becomes true, it switches on.

It then remains true across subsequent evaluations until the right condition becomes true.

After that, it switches off for the next evaluation.

It's particularly useful for extracting sections of text between markers.

The strange part is that the operator itself maintains state.

You don't have to declare a separate boolean variable.

You don't have to track whether you've encountered the start marker.

You don't have to manage the transition explicitly.

Perl does it inside the operator.

And each occurrence of the scalar flip-flop operator maintains its own state, even across calls to a subroutine containing it.

Now consider that ... is also a flip-flop operator in scalar context, but differs in exactly when it tests the ending condition.

Because apparently one stateful range operator wasn't sufficient.

To be fair, this is beautifully suited to Perl's text-processing origins.

It's concise, expressive, and genuinely useful.

It's also an operator with a memory.

And that isn't something you encounter every day.

The lesson: In Perl, an operator can be more than a calculation. It can be a miniature state machine hiding behind two dots.

6. Perl's Variable Names Look Like Someone Fell Asleep on the Keyboard

We've saved the most visually Perl-like example for last.

Most languages encourage descriptive variable names.

Things like:

current_user
error_message
record_count
Enter fullscreen mode Exit fullscreen mode

Perl supports those too.

But Perl also has an extensive collection of special variables.

And some of their names are... unconventional.

Meet the family:

$_
$!
$?
$@
$/
$$
$^O
Enter fullscreen mode Exit fullscreen mode

This isn't obfuscated code.

These are legitimate built-in Perl variables.

And they all mean different things.

A few highlights:

$_ is the default input and pattern-matching variable.

$! contains information about the most recent system error, with useful string and numeric interpretations.

$? contains status information from the last child process or certain related operations.

$@ contains the exception message from the last eval.

$/ controls the input record separator.

$$ is the current process ID.

$^O identifies the operating system Perl was built for.

That's quite a collection.

And some of them are genuinely powerful.

For example, consider:

$_ = "  hello  ";

s/^\s+|\s+$//g;

print "$_\n";
Enter fullscreen mode Exit fullscreen mode

Output:

hello
Enter fullscreen mode Exit fullscreen mode

Notice something?

We didn't explicitly pass a string to the substitution operator.

Perl simply used $_.

Many Perl operations default to working on that variable when you don't specify another target.

It's convenient.

It's concise.

And it means an extraordinary amount of behavior can be hiding in code that barely seems to contain any variables.

But let's go further.

Remember $/?

By default, it tells Perl to read input one line at a time.

Change it, and you can change how input records are read.

For example:

{
    local $/ = undef;
    my $contents = <$fh>;
}
Enter fullscreen mode Exit fullscreen mode

Assuming $fh is an open filehandle, this reads the remaining contents of the file at once rather than one line at a time.

That's it.

Set a punctuation-named variable to undef, and you've changed the input-reading behavior.

The local keeps that change confined to the block's dynamic scope.

It's an elegant little idiom once you understand it.

Before that, it looks like someone has accidentally assigned undef to a typo.

And if you dislike all these punctuation variables, Perl provides the English module, which offers more descriptive aliases for many of them.

For example, you can use $OS_ERROR instead of $!.

Which means Perl provides readable variable names.

It just also provides the option not to use them.

Because TMTOWTDI.

The lesson: Perl's punctuation variables aren't arbitrary decoration. They're compact interfaces to important runtime state and built-in behavior.

Whether that makes them brilliant or terrifying is largely a matter of personal temperament.

And This Is Only Part One

Let's take stock.

We've encountered a language where:

  • The context of an expression can completely change what it does.
  • A string can represent numeric zero while evaluating as true.
  • Checking whether something exists can create part of it.
  • A loop variable can silently alias the original data.
  • An operator can maintain its own internal state.
  • Punctuation marks can act as variables controlling the interpreter's behavior.

And everything we've covered is documented, intentional behavior.

Not bugs.

Not accidents.

Features.

But here's something important.

For all the jokes about Perl being unreadable, much of this weirdness reflects a coherent philosophy.

Larry Wall approached programming-language design with a strong interest in human language.

Natural languages are full of context, ambiguity, shortcuts, implied subjects, and multiple ways of saying the same thing.

Perl deliberately embraces many of those characteristics.

Rather than forcing every programmer to express every operation in a single standardized style, it provides tools for writing concise, expressive code suited to the task at hand.

Sometimes that produces wonderfully elegant solutions.

Sometimes it produces code that looks like a cat walked across the keyboard while holding down Shift.

And sometimes those are the same thing.

Coming in Part Two: The Deep Magic

If you think we've exhausted Perl's peculiarities, we haven't even started on the truly alarming stuff.

And perhaps we'll even discuss how Perl 6 eventually became Raku, a separate language with its own fascinating history.

Because, naturally, one Perl wasn't enough.

But that's for Part Two.

For now, I'll leave you with this.

Perl has been called the Swiss Army chainsaw of programming languages.

It can accomplish an extraordinary amount with very little code.

It can also make that code almost incomprehensible to the next person who encounters it.

Which might be you, six months later.

Top comments (0)