Perl 5 Is Pretty Awesome, Actually

Perl 5 was the first programming language I really mastered. There are lots of things to love about it in 2024.

Stability

Perl 5 is stable. I have Perl code, some of which I wrote, that is nearly old enough to run for the US House of Representatives. Yet it still runs on a recent Perl 5, with few or no changes. I was writing Python professionally during the 2 to 3 transition, and I'm still butthurt about how terribly that transition went. And I suspect that lots of code running on Python 3 has subtle little bugs hiding and lying in wait for the unwary, especially bugs related to strings and the change to the division operator.

Lexical Scope!

Yeah, Perl has it; another popular scripting language from the 90s doesn't. Perl has dynamic scope, too, if you need it. It's the "have your cake and eat it too" language.

A Culture of Baked-in Documentation

POD and perldoc got this very, very right. POD is even typically rendered as manpages at install time, so I can do things like `man JSON::XS` to see the documentation for the `JSON::XS` module as a manpage. The `perldoc` tool is also excellent.

Other languages have similar things now. Python has `pydoc`, and here is part of what I see when I run `pydoc json`:

Help on package json:

NAME
    json

MODULE REFERENCE
    https://docs.python.org/3.12/library/json.html

    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

Let's compare that with the first few lines of output from `perldoc JSON::XS`, shall we?

NAME
    JSON::XS - JSON serialising/deserialising, done correctly and fast

    JSON::XS - 正しくて高速な JSON シリアライザ/デシリアライザ
    (http://fleur.hio.jp/perldoc/mix/lib/JSON/XS.html)

SYNOPSIS
     use JSON::XS;

     # exported functions, they croak on error
     # and expect/generate UTF-8

     $utf8_encoded_json_text = encode_json $perl_hash_or_arrayref;
     $perl_hash_or_arrayref  = decode_json $utf8_encoded_json_text;

     # OO-interface

     $coder = JSON::XS->new->ascii->pretty->allow_nonref;
     $pretty_printed_unencoded = $coder->encode ($perl_scalar);
     $perl_scalar = $coder->decode ($unicode_json_text);

So the `pydoc` documentation starts off by telling me that it was automatically generated and might be inaccurate, while the Perl documentation shows me a clear example of how to use the thing. That's because documentation was strongly emphasized in the Perl community. Somewhere long ago, I read: "You can't have a good module without good documentation in POD (plain old documentation) format." Perl hackers also appeared to be a bunch who prided themselves on their literacy, while also having a well-developed sense of humor.

Perl also had a strong culture of testing. Among Larry Wall's inventions is TAP, the Test-Anything Protocol, which shows up in other languages than just Perl.

It's fast!

That's because Perl uses reference counting and a pay-as-you-go model for freeing memory. When an object's reference count reaches 0, it is immediately freed. Instead of paying an amortized cost over time, or dealing with a "stop the world to take out the trash" situation every so often, you pay the whole penalty for freeing an object and its object graph at the time you delete its last reference. Some GC nerds seem to think that this isn't real garbage collection. But the advantage is that it can give you a really strong intuition about your program's memory performance characteristics as you are writing and testing code. At least, that's what I noticed when I was writing Perl regularly twenty-ish years ago.

It turns out that the Perl interpreter even starts up faster. Who doesn't love a synthetic benchmark? Here's mine. "How fast can various popular scripting languages start, print "Hello world!", and terminate? Here's the code to answer that question:

#!/bin/bash
# Stupid synthetic benchmark comparing scripting languages by the time it takes to print "hello world."
# WTFPL
set -e
set -o nounset

# We actually give 0 fucks about what gets printed to stdout; may as well send it to /dev/null:

exec > /dev/null

run_lua() {
  lua5.3 -e 'print("Hello, world!\n")'
}
run_perl() {
  perl -e 'print("Hello, world!\n")'
}
run_ruby() {
  ruby -e 'print("Hello, world!\n")'
}
run_python() {
  python -c 'print("Hello, world!")'
}

# Time a bash function.
tbf() {
  func="$1"
  name="$2"
  iterations="$3"
  start_time="${EPOCHREALTIME}"
  for i in $(seq 1 "$iterations") ; do
      $func
  done
  end_time="${EPOCHREALTIME}"
  delta="$(printf 'res=1000*(%s-%s)\nscale=0\nprint(res/1)\n' "$end_time" "$start_time" |bc)"
  printf '%d iterations of %s took %d milliseconds.\n' "$iterations" "$name" "$delta" 1>&2
}

case "$1" in
    lua|perl|python|ruby) tbf "run_$1" "$1" 100 ;;
    the_works)
	for i in lua perl python ruby ; do
	    "$0" "$i"
	done
	;;
    *)
	printf "Go home, you're drunk!\\n" 1>&2
	exit 1
	;;
esac

And here are the results:

Script started on 2024-12-11 05:46:15-08:00 [TERM="screen.linux" TTY="/dev/pts/6" COLUMNS="80" LINES="25"]
chris@beast:/home/chris $ ~/bench.sh the_works
100 iterations of lua took 276 milliseconds.
100 iterations of perl took 421 milliseconds.
100 iterations of python took 3663 milliseconds.
100 iterations of ruby took 12107 milliseconds.
chris@beast:/home/chris $ exit

Script done on 2024-12-11 05:46:42-08:00 [COMMAND_EXIT_CODE="0"]

Take that for what you will.

Perl: a language that knows how to dye properly!

I love Perl's `die` function. Love it so much that I roll my own if there's not an equivalent baked into whatever language I'm using at the moment.

Conclusion

I think I've just about convinced myself that I should get back into Perl. Maybe I've convinced you, too.