In Perl, subroutines are values — they can be stored in variables, hashes, and arrays just like strings or numbers. A reference to a subroutine is called a coderef. A dispatch table is a hash whose values are coderefs, letting you select and call behaviour by name at runtime.
This is one of the most important intermediate Perl patterns. Instead of writing a long chain of if ($op eq 'add') { ... } elsif ($op eq 'sub') { ... }, you look up the right function directly in a hash — O(1) lookup, cleaner code, and easily extensible.
$calc{mod} = sub { ... }) without touching existing code. This is the open/closed principle — open to extension, closed to modification.
# Each value is an anonymous sub (coderef) — note the sub { } syntax my %calc = ( add => sub { $_[0] + $_[1] }, # $_[0], $_[1] are args to THIS anon sub div => sub { die "Division by zero\n" if $_[1] == 0; $_[0] / $_[1]; }, ); # Dispatch: $calc{op} retrieves the coderef, ->() calls it sub calculate { my ($op, $a, $b) = @_; die "Unknown op: $op\n" unless exists $calc{$op}; return $calc{$op}->($a, $b); # ->() dereferences and calls } # Add a new operation dynamically — no rewriting needed $calc{mod} = sub { $_[0] % $_[1] };
sub { ... } without a name creates an anonymous subroutine and returns a coderef — a scalar reference to executable code. Assigning it to a hash value stores that reference.$_[0], $_[1] inside the anonymous sub refer to that sub's own @_ argument list, not the outer function's arguments. Each sub invocation has its own @_.$calc{$op}->($a, $b) — the -> dereference operator calls the coderef. You can also write &{$calc{$op}}($a, $b) or $calc{$op}($a, $b) (the latter works in newer Perl but ->() is clearest).exists-check before dispatching. Calling a nonexistent key returns undef, and calling undef as a coderef gives a cryptic "Not a CODE reference" error. The exists check gives you a meaningful message.$calc{$op}($a, $b) looks fine but Perl interprets this as a named function call named by the string in $op, not a coderef call. Use ->() to be explicit and correct.
Perl's error handling uses die to throw and eval { } to catch. After the eval block, $@ contains the error — either a string or a blessed object (exception object). If no error occurred, $@ is empty.
die can throw any scalar — a string, a number, or a blessed hash reference. This means you can create rich exception objects with codes, stack traces, and methods. The Carp module's croak is like die but reports the error from the caller's perspective, which is more useful in library code.
use Carp qw(croak); # croak: like die, but blames the CALLER, not this function sub divide { my ($a, $b) = @_; croak "Cannot divide by zero" if $b == 0; return $a / $b; } # eval { } is the try-block — catches any die/croak inside eval { divide(10, 0) }; # CRITICAL: copy $@ immediately — it can be cleared by object DESTROY methods if (my $err = $@) { print "Caught: $err\n"; } # Exception objects: die with a blessed reference instead of a string package MyException; sub new { my ($class, %args) = @_; return bless { message => $args{message}, code => $args{code} }, $class; } package main; eval { die MyException->new(message => "Not found", code => 404); }; if (my $e = $@) { # ref() tells you if $@ is an object vs a plain string if (ref $e && $e->isa('MyException')) { printf "Object error [%d]: %s\n", $e->{code}, $e->{message}; } }
eval { } (block form) catches runtime exceptions. If anything inside calls die, execution jumps to after the closing } and $@ is set. If nothing dies, $@ is set to "".$@ immediately: if (my $err = $@). If you call any other function before checking $@, it can be silently cleared. Copying to a lexical variable is always the right pattern.croak vs die: both throw errors, but die points to the line where die is written, while croak points to the line that called the function. In library code, croak gives callers more useful error messages.ref $e && $e->isa(...): always check ref $e first. If $e is a plain string, calling ->isa() on it would itself die. The && short-circuits, so isa is only called if ref returns a true value (i.e., if $e is a reference).Try::Tiny (CPAN) which provides try { } catch { } syntax and correctly handles all the edge cases of eval/$@ automatically.
A closure is a subroutine that "closes over" (captures) variables from the scope in which it was created. Those captured variables continue to exist as long as the closure does — even after the outer function has returned. This is how Perl implements private state, factory functions, and iterators without needing a full class.
Each time a factory function runs, it creates a new, independent copy of the captured variables. Two counters from the same factory don't share state.
# make_counter RETURNS a closure — not a value, but a function sub make_counter { my ($start, $step) = @_; my $count = $start; # <-- THIS is captured by the closures below return { # Each anonymous sub closes over $count and $start/$step next => sub { $count += $step }, # modifies $count reset => sub { $count = $start }, # resets $count to original value => sub { $count }, # reads $count }; } # c1 and c2 are INDEPENDENT — they each have their own $count my $c1 = make_counter(0, 1); my $c2 = make_counter(100, -10); # Memoization: a closure that remembers previous results sub memoize { my ($fn) = @_; my %cache; # private cache captured by this closure return sub { my $key = join(',', @_); return $cache{$key} if exists $cache{$key}; return $cache{$key} = $fn->(@_); }; }
make_counter(0, 1) runs, Perl allocates a new $count lexical. The three anonymous subs returned by this call all share that specific instance of $count.make_counter() again creates a completely fresh $count. $c1 and $c2 are independent — incrementing $c1 has no effect on $c2.%cache hash is private — callers of the memoized function cannot access or clear it. This is encapsulation without a class.return $cache{$key} = $fn->(@_) — this works because assignment in Perl is an expression that returns the assigned value. It computes, stores, and returns in a single line.Processing structured text files is Perl's original killer use case. The key idiom here is the hash slice assignment — a concise way to build a named record from parallel arrays of keys and values — and the hash-of-arrays structure for grouping.
# Read and parse; @fields holds column names from header my $header = <$fh>; chomp $header; my @fields = split /,/, $header; # ("name","dept","salary") my %by_dept; while (my $line = <$fh>) { chomp $line; next unless $line =~ /\S/; # skip blank lines # Hash slice: @rec{@fields} = ... fills multiple hash keys at once # @fields = ("name","dept","salary") # split gives ("Alice","Eng","95000") # Result: $rec{name}="Alice", $rec{dept}="Eng", $rec{salary}="95000" my %rec; @rec{@fields} = split /,/, $line; # push a REFERENCE to the record into the dept's array push @{ $by_dept{ $rec{dept} } }, \%rec; } # Accessing grouped data: $by_dept{Eng} is an arrayref of hashrefs for my $dept (sort keys %by_dept) { my @salaries = map { $_->{salary} } @{ $by_dept{$dept} }; }
@rec{@fields} = (...) — notice the @ sigil on %rec. When you access multiple hash keys at once, you use @hash{list} (array sigil) because you're getting/setting a list of values. This is a core Perl idiom.push @{ $by_dept{ $rec{dept} } }, \%rec — nested dereference syntax. $by_dept{$rec{dept}} is (or will be) an array reference. @{ ... } dereferences it so push can append to it. Perl auto-vivifies the arrayref if it doesn't exist yet.\%rec stores a reference to the hash, not a copy. This is important — without the backslash, %rec would be flattened into a list and the structure would be lost.next unless $line =~ /\S/ — \S matches any non-whitespace. This pattern correctly skips blank lines and lines with only spaces/tabs, which next unless $line would miss if $line was " \n".Text::CSV or Text::CSV_XS. Plain split /,/ breaks on quoted fields like "Smith, John" or embedded commas. This example is for teaching the Perl idioms, not production CSV parsing.
List::Util ships with every Perl installation (it's a core module). Its functions are implemented in C (fast) and replace hand-written loops. Knowing the full API makes your code shorter, faster, and more expressive.
use List::Util qw(sum sum0 product min max first any all none reduce uniq pairs); my @nums = (3, 1, 4, 1, 5, 9, 2, 6); # sum() returns undef on empty list — sum0() returns 0 (safer) sum(@nums); # 31 sum0(); # 0 (not undef) product(@nums); # 6480 # first — stops at the FIRST match (unlike grep which scans all) my $big = first { $_ > 5 } @nums; # 9 (first element > 5) # any/all/none — boolean, also short-circuit for performance any { $_ % 2 == 0 } @nums; # true (stops at first even) all { $_ > 0 } @nums; # true (stops if any fail) none { $_ < 0 } @nums; # true (stops if any negative found) # reduce — folds list with $a=accumulator, $b=current element # This computes GCD of a list using Euclidean algorithm my $gcd = reduce { my ($x, $y) = ($a, $b); # copy first; $a and $b are aliases ($x, $y) = ($y, $x % $y) while $y; $x; } 48, 36, 24; # uniq removes duplicates (keeps first occurrence) # Sort FIRST if you want all duplicates removed, not just consecutive ones my @unique = uniq sort {$a<=>$b} @nums; # 1 2 3 4 5 6 9
first vs grep: both search a list with a predicate, but first stops immediately when a match is found (short-circuit). Use first when you only need one result from a potentially large list.reduce's $a and $b are package globals (like sort's $a/$b), not lexicals. That's why you must copy them before modifying: writing $a = something inside reduce would corrupt the accumulator itself.sum() vs sum0(): on an empty list, sum() returns undef (mathematically correct — sum of nothing is undefined). sum0() returns 0 (conveniently safe for accumulating). Choose based on whether an empty input is valid in your context.uniq removes consecutive duplicates. If your input is (1, 2, 1), uniq returns (1, 2, 1) — only the middle pair of identical adjacent elements would be collapsed. sort before uniq to get truly unique values.When you sort by a key that's expensive to compute (a regex, a stat call, a sum), a naïve sort { compute($a) <=> compute($b) } calls compute() twice per comparison — O(N log N) times total. The Schwartzian transform computes each key exactly once, then sorts on the cached values.
Named after Randal Schwartz, it chains three operations without intermediate variables: map to decorate → sort on decoration → map to undecorate. Read it bottom-to-top: the last map feeds into sort, which feeds into the first map.
# Goal: sort filenames in natural order (report1, report2, report10) # Not alphabetical (report1, report10, report2) my @nat_sorted = map { $_->[0] } # Phase 3: unwrap — discard the cached keys sort { $a->[1] cmp $b->[1] # Phase 2: compare prefix alphabetically || $a->[2] <=> $b->[2] # then suffix numerically } map { # Phase 1: decorate with computed keys my ($prefix, $num) = $_ =~ /^([a-z]+)(\d*)/i; [$_, $prefix, $num || 0] # [$original, $alpha_key, $num_key] } @files; # input — read bottom-to-top # The || in sort: if prefixes are equal (cmp returns 0), use numeric sort # 0 is false in Perl, so || falls through to the second comparison
@files is the input → first (bottom) map decorates each element into [$original, $key1, $key2] → sort compares only on the precomputed keys → second (top) map strips off the keys, returning only the original.cmp vs <=>: cmp is the string spaceship operator (returns -1, 0, 1 from string comparison). <=> is the numeric spaceship operator. The sort block uses || to chain comparisons: first by string prefix, then by number if strings are equal.sort { expensive($a) <=> expensive($b) }? For N=100 elements, sort does ~700 comparisons. That's 1400 calls to expensive(). The Schwartzian makes exactly 100 calls — one per element.$num || 0 handles files without a number suffix (like notes.txt). If the regex capture (\d*) matches nothing, $num is undef. The || 0 provides a safe numeric default.printf prints formatted output directly; sprintf returns the formatted string. The format string uses conversion specifiers starting with %. Between % and the type letter, you can specify flags, width, and precision.
# Format anatomy: %[flags][width][.precision]type # # flags: - (left-align) + (force sign) 0 (zero-pad) space # width: minimum field width (pads with spaces by default) # .precision: max chars for strings, decimal places for floats # type: d(int) f(float) s(string) e(sci) g(smart) x(hex) b(bin) printf "%d\n", 42; # "42" — basic integer printf "%8d\n", 42; # " 42" — right-aligned in 8-wide field printf "%-8d|\n", 42; # "42 |" — left-aligned (- flag) printf "%08d\n", 42; # "00000042" — zero-padded (0 flag) printf "%+d\n", 42; # "+42" — forced sign (+ flag) printf "%.2f\n", 3.14159; # "3.14" — 2 decimal places printf "%10.3f\n", 3.14159; # " 3.142" — width 10, 3 decimals printf "%g\n", 0.00001; # "1e-05" — %g picks shorter of %f or %e # Multiple values in one printf — positional printf "%-12s %6d %9.2f %12.2f\n", "Widget A", 1024, 9.99, 1024 * 9.99; # sprintf: build the string without printing my $label = sprintf "[%05d] %-20s", 42, "Alice";
%.10s) truncates to that many characters. On floats, it sets decimal places.%g is the smart float format — it automatically chooses between %e (scientific notation) and %f (fixed), picking whichever is shorter. Useful when you don't know the magnitude of your data in advance.%-12s (left-align strings) and %8d/%10.2f (right-align numbers). This matches natural reading conventions and produces clean tables without manual padding.sprintf vs printf: use sprintf when you need the formatted string as a value (storing in a variable, appending to another string, passing to a function). Use printf for immediate output.Moose replaces the tedious boilerplate of bare-metal Perl OOP (bless, manually written accessors, manual @ISA inheritance) with a clean declarative syntax. You describe what a class has and does, not how to implement the plumbing.
Key concepts: attributes (has) declare properties with type constraints and defaults. Roles (Moose::Role) are like interfaces that also provide implementation — a class can consume multiple roles. Method modifiers (before, after, around) let you wrap methods without overriding them entirely.
# has 'name' declares an attribute with full config has 'name' => ( is => 'ro', # read-only: getter generated, no setter isa => 'Str', # type constraint: dies if not a string required => 1, # must be passed to new() ); has 'sound' => ( is => 'rw', # read-write: getter AND setter generated isa => 'Str', default => '...', # default value if not given to new() ); # Role: a reusable bundle of behaviour (like an interface + implementation) package Printable; use Moose::Role; requires 'to_string'; # any class using this role MUST implement to_string sub print_self { print $_[0]->to_string() . "\n" } # Subclass Dog extends Animal, overrides the 'sound' default package Dog; use Moose; extends 'Animal'; has '+sound' => ( default => 'Woof!' ); # + prefix modifies parent's attr # before modifier: runs BEFORE speak() without replacing it before 'speak' => sub { print "[Dog wags tail]\n"; # runs, then original speak() runs };
is => 'ro' vs 'rw': ro generates only a getter method (same name as the attribute). rw generates a getter and a setter. Prefer ro for immutable data — it makes your objects safer and easier to reason about.isa => 'Str' is a type constraint. Moose will call die at object construction time if you pass a value of the wrong type (e.g. name => 42). Built-in types include Str, Int, Num, Bool, ArrayRef, HashRef, CodeRef.requires lets a role declare what the consuming class must provide — a contract. Multiple roles can be consumed; multiple inheritance in OOP is messy, but multiple roles compose cleanly.before/after/around are method modifiers. before runs code before the original. after runs after. around receives the original method as a coderef and can control if/when it's called — useful for logging, validation, or caching.Moo — it's a 95%-compatible subset that's significantly faster to load, ideal for scripts and smaller projects.
JSON::PP is a pure-Perl JSON module in Perl's core since 5.14. It maps between Perl data structures and JSON: Perl hashrefs become JSON objects, arrayrefs become JSON arrays, scalars become strings or numbers. The tricky part is handling JSON true/false/null, which have no direct Perl equivalent.
use JSON::PP; # Chain config methods: utf8 + pretty + canonical (sorted keys) my $json = JSON::PP->new->utf8->pretty->canonical; # JSON true/false/null must use JSON::PP constants — not 1/0/undef! my %config = ( debug => JSON::PP::true, # encodes as JSON true (not "true" or 1) timeout => JSON::PP::null, # encodes as JSON null (not undef/"") ports => [8080, 8443], # arrayref -> JSON array db => { host => "localhost" }, # hashref -> JSON object ); # encode() takes a REFERENCE (not a plain hash) my $str = $json->encode(\%config); # decode() returns a reference — dereference to use my $data = $json->decode($str); print $data->{db}->{host}; # "localhost" # Always wrap decode() in eval — invalid JSON throws an exception my $parsed = eval { $json->decode('{ bad }') }; warn "JSON error: $@" if $@;
true, false, null. Perl doesn't. JSON::PP::true is a special object that stringifies as 1 and encodes as JSON true. Using plain Perl 1 would encode as a number, not a boolean — which breaks strict JSON consumers.encode(\%config) takes a reference. A plain hash in Perl is a flat list; encode(%hash) would see a list, not a structure. Always pass \%hash or \@array to encode().canonical makes the encoder sort hash keys alphabetically. Without it, Perl's hash key order is random. Use canonical whenever you need reproducible output (tests, checksums, debugging).JSON::PP is pure Perl and slow on large payloads. Install JSON::XS (C-based, ~60x faster) or use the JSON wrapper module which automatically uses JSON::XS if available, falling back to JSON::PP.Getopt::Long parses the script's @ARGV array, extracting --long-style options and their values into Perl variables. It handles type coercion (strings, integers, floats), boolean flags, accumulating repeated options into arrays, and short aliases.
use Getopt::Long; # Declare variables with defaults BEFORE GetOptions my $input = '-'; # '-' means stdin by convention my $count = 10; my $verbose = 0; my @tags; # will accumulate multiple --tag values GetOptions( 'input=s' => \$input, # =s requires a string argument 'count=i' => \$count, # =i requires an integer 'tag=s' => \@tags, # array ref: each --tag adds to @tags 'verbose|v' => \$verbose, # boolean flag; |v adds -v short alias 'help|h' => sub { print "Usage...\n"; exit }, # inline handler ) or die "Usage error. Try --help\n"; # After GetOptions, @ARGV holds any remaining non-option arguments # e.g. "script.pl --count 5 file1.txt file2.txt" leaves @ARGV = ("file1.txt","file2.txt") # Type specifiers: =s string =i integer =f float =o extended integer # Optional value: :s :i (value is optional; undef if not given) # No value (flag): just the name, no = sign
GetOptions modifies @ARGV. After it runs, all recognized options and their values are removed from @ARGV. What remains are positional arguments (filenames, etc.). This is the conventional way to handle both options and file arguments.\@tags: when the spec ends in =s and the destination is an array ref, each occurrence of --tag VALUE appends VALUE to the array. --tag perl --tag scripting gives @tags = ('perl', 'scripting').'verbose|v': the pipe separates the long name from the short alias. --verbose and -v both set $verbose = 1. You can have multiple aliases: 'verbose|v|V'.or die after GetOptions: GetOptions returns false if it encounters an unknown option or a type mismatch. The or die gives the user a helpful message. Without it, the script silently continues with wrong inputs.These features separate intermediate regex use from basic pattern matching. Lookahead and lookbehind are zero-width assertions — they check context without consuming characters. Backreferences refer back to what a capture group matched earlier in the same pattern. tr/// (transliterate) is a character-by-character mapping — fundamentally different from regex substitution. qr// compiles a regex into a reusable value.
# --- Lookahead: match X only when followed by Y --- # (?=Y) asserts Y is ahead WITHOUT consuming it my $text = "100USD 200EUR 75USD"; my @usd = ($text =~ /(\d+)(?=USD)/g); # captures 100, 75 (not 200) # --- Lookbehind: match X only when preceded by Y --- # (?<=Y) asserts Y is behind WITHOUT consuming it my @codes = ($text =~ /(?<=\d)([A-Z]{3})/g); # USD, EUR, USD # --- Negative forms --- # (?!Y) — NOT followed by Y # (?<!Y) — NOT preceded by Y # --- Backreference: \1 refers to what group 1 captured --- # Remove duplicate words: \b(\w+)\s+\1\b — word, space(s), SAME word my $s = "the the quick fox fox"; $s =~ s/\b(\w+)\s+\1\b/$1/gi; # "the quick fox" — /i for case-insensitive # --- tr/// (transliterate) — character-by-character mapping --- # tr/SEARCH/REPLACE/ — each char in SEARCH maps to same-position char in REPLACE my $str = "Hello World"; $str =~ tr/a-z/A-Z/; # uppercase (like uc() but in-place) my $digits = ($str =~ tr/0-9//); # tr returns COUNT of chars matched # --- qr// — compiled, reusable regex value --- my $email_re = qr/^[\w.+-]+\@[\w-]+\.\w{2,}$/; # compiled once my @valid = grep { $_ =~ $email_re } @candidates; # reused each iteration
/(\d+)(?=USD)/, the engine finds digits, then checks (without advancing the position) that USD follows. The match includes only the digits — USD is not in the capture or in the overall match.\1 in a pattern (not substitution) must match the exact same text that group 1 captured. /\b(\w+)\s+\1\b/ will only match the the, not the a. In the replacement side of s///, use $1 (not \1).tr/// vs s///: tr/// maps characters one-to-one (like a Caesar cipher). It does not support regex metacharacters — tr/\d// removes literal \ and d, not digits. Use s/\d//g for that. tr///'s return value is the count of characters translated.qr// compiles a regex once. When you use a regex inside a loop, Perl recompiles it each iteration. qr// compiles it once and returns a regex object that can be stored and reused. This is important for performance when filtering large lists.(?<=...)) must be fixed-width — you cannot use +, *, or ? inside it. Use (?<=\d\d\d) not (?<=\d+). Variable-length lookbehind was added in Perl 5.30 with (*LOOKBEHIND).
DBI is the standard Perl database abstraction layer. It works with any database (SQLite, MySQL, PostgreSQL, Oracle…) through database-specific drivers (DBD::SQLite, DBD::mysql, etc.). The API is identical regardless of the backend — only the DSN connection string changes.
The critical concept is placeholders (? in SQL): never interpolate user data directly into SQL strings. Placeholders prevent SQL injection and also allow the database to cache the query plan.
use DBI; # Connect: DSN (Data Source Name) format: "dbi:Driver:params" my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', # DSN — change for MySQL/Postgres '', '', # username, password { RaiseError => 1, AutoCommit => 1 } # RaiseError: die on errors ) or die DBI->errstr; # do() for one-shot statements (DDL, simple DML) $dbh->do('CREATE TABLE t (id INTEGER, name TEXT)'); # prepare() compiles the SQL; ? are placeholders — NEVER interpolate user data my $ins = $dbh->prepare('INSERT INTO t VALUES (?, ?)'); # Transaction: wrap multiple inserts — all succeed or all roll back eval { $dbh->{AutoCommit} = 0; # begin transaction $ins->execute(1, 'Alice'); $ins->execute(2, 'Bob'); $dbh->commit; # commit all at once }; if ($@) { $dbh->rollback; die $@ } $dbh->{AutoCommit} = 1; # Fetch options: # fetchrow_arrayref — fastest, one row as array ref # fetchrow_hashref — row as hash ref (column_name => value) # fetchall_arrayref — all rows as array of arrayrefs my $sth = $dbh->prepare('SELECT * FROM t WHERE id > ?'); $sth->execute(0); while (my $row = $sth->fetchrow_hashref) { print "$row->{id}: $row->{name}\n"; } # selectrow_array — convenience for single-row results my ($count) = $dbh->selectrow_array('SELECT COUNT(*) FROM t'); $dbh->disconnect;
RaiseError => 1 is essential. Without it, every DBI call can fail silently and you must check $dbh->err after every statement. With RaiseError => 1, failures throw exceptions that propagate naturally and can be caught with eval."SELECT * FROM t WHERE name = '$user_input'". If $user_input is "'; DROP TABLE t; --", you have a disaster. Placeholders pass the value separately from the SQL structure — the driver escapes it safely.AutoCommit => 0): every row in the batch either succeeds or all are rolled back. This prevents partial writes. The pattern is always: set AutoCommit = 0, do work inside eval, commit on success, rollback in the $@ handler, reset AutoCommit = 1.prepare then execute in a loop is more efficient than calling do() in a loop. The prepared statement is compiled once; execute() just binds new values each time. For bulk inserts, this can be 10x faster than repeated do() calls.dbi:SQLite:dbname=file.db ·
MySQL: dbi:mysql:database=mydb;host=localhost ·
PostgreSQL: dbi:Pg:dbname=mydb;host=localhost
The rest of the DBI API is identical.