Perl Part I · Module 7 · References
Code References and Dispatch Tables
The three reference kinds so far have all referred to data. A code reference refers to a subroutine, which means behaviour can be stored in a scalar, kept in an array, passed to a function, or — the case that makes this chapter worth reading — used as the value in a hash. That last one gives you a dispatch table: instead of a chain of elsif branches comparing a command name against twelve literals, you write one hash whose keys are the command names and whose values are the subroutines that handle them, and a single line that looks the command up and calls it. The table is data, so it can be built in pieces, extended at run time, and checked for completeness. The elsif chain can do none of those.
Advanced17 min readPerlPart IReferencesSubroutines
Part I · Module 7 · Chapter 7.11 · Code References and Dispatch Tables
1. Two Ways to Make One
use strict;
use warnings;
sub greet { return "hello " . ($_[0] // "world") }
my $named = \&greet;
my $anon = sub { return "doubled " . ($_[0] * 2) };
printf "named ref kind : %s\n", ref($named);
printf "anon ref kind : %s\n", ref($anon);
printf "named call : %s\n", $named->("alu");
printf "anon call : %s\n", $anon->(21);
printf "no-arrow call : %s\n", &$named("fifo");named ref kind : CODE
anon ref kind : CODE
named call : hello alu
anon call : doubled 42
no-arrow call : hello fifo\&name takes a reference to an existing named subroutine. The & sigil is required — \greet would call it and take a reference to the result.
sub { ... } builds a subroutine with no name and returns a reference to it in one step. This is Chapter 7.5's anonymous constructor idea applied to code, and it is the form you will write far more often.
$ref->(args) calls it. Same arrow as everywhere else in this module — go through the reference — with parentheses instead of a subscript.
The last line shows &$named("fifo") also works. You will see it in older code; the arrow form is clearer and is what to write.
Note that arguments and return values are entirely ordinary. $_[0] inside the subroutine is the first argument exactly as in a named sub, which is Module 10's subject. A code reference changes how you reach a subroutine, not how subroutines work.
2. The Dispatch Table
This is the pattern that justifies the chapter.
use strict;
use warnings;
my %handler = (
read => sub { my ($addr) = @_; return sprintf("READ 0x%04X", $addr) },
write => sub { my ($addr, $val) = @_; return sprintf("WRITE 0x%04X = 0x%02X", $addr, $val) },
reset => sub { return "RESET" },
);
my @commands = (
["read", 0x40],
["write", 0x44, 0xFF],
["reset"],
["erase", 0x00],
);
for my $cmd (@commands) {
my ($op, @args) = @$cmd;
my $fn = $handler{$op} // sub { return "UNKNOWN op '$op'" };
printf "%-6s -> %s\n", $op, $fn->(@args);
}
printf "handlers defined: %d [%s]\n", scalar(keys %handler), join(" ", sort keys %handler);read -> READ 0x0040
write -> WRITE 0x0044 = 0xFF
reset -> RESET
erase -> UNKNOWN op 'erase'
handlers defined: 3 [read reset write]The whole dispatch is these two lines:
my $fn = $handler{$op} // sub { ... fallback ... };
$fn->(@args);Compare the elsif version of the same thing:
if ($op eq "read") { ... }
elsif ($op eq "write") { ... }
elsif ($op eq "reset") { ... }
else { ... }At three operations the difference is cosmetic. At twelve it is not, and the advantages compound:
- The table is data. You can count it, list it, check a configuration file's operations against
keys %handler, or build it from several sources. - Adding an operation is adding a key, not editing a chain and hoping you got the
elsifin the right place. - Lookup is a hash lookup, not a sequence of string comparisons.
- The fallback is explicit.
//supplies a handler for anything not in the table, so an unknown operation produces a clear message rather than falling off the end of the chain.
Note each handler takes a different number of arguments — read takes one, write takes two, reset takes none — and @args flows through unexamined. The dispatcher does not need to know the arity of anything it calls.
3. Checking the Table Against Reality
Because the table is data, you can validate it — which an elsif chain cannot offer.
use strict;
use warnings;
my %handler = (
read => sub { "READ" },
write => sub { "WRITE" },
reset => sub { "RESET" },
);
my @required = qw(read write reset erase);
my @missing = grep { !exists $handler{$_} } @required;
my @extra = grep { my $h = $_; !grep { $_ eq $h } @required } sort keys %handler;
printf "required : %d [%s]\n", scalar(@required), join(" ", @required);
printf "defined : %d [%s]\n", scalar(keys %handler), join(" ", sort keys %handler);
printf "missing : %d [%s]\n", scalar(@missing), join(" ", @missing);
printf "extra : %d [%s]\n", scalar(@extra), join(" ", @extra);
die "refusing to run: no handler for @missing\n" if @missing;required : 4 [read write reset erase]
defined : 3 [read reset write]
missing : 1 [erase]
extra : 0 []refusing to run: no handler for erase255The script refused to start because one required operation had no handler. That check is four lines and it runs before any command is processed — which is the difference between failing at startup with a clear message and failing halfway through a run on the one input that happened to use the missing operation.
This is the real argument for dispatch tables in engineering scripts. A chain of elsif cannot be asked "which operations do you handle?", so the question never gets asked and the gap is found by a user.
4. Closures: a Subroutine That Remembers
An anonymous subroutine can capture a lexical variable from where it was created, and it keeps that variable alive — the same reference-counting rule from Chapter 7.2 §3.
use strict;
use warnings;
sub make_counter {
my ($n) = @_;
return sub { return $n++ };
}
my $a = make_counter(5);
my $b = make_counter(100);
printf "a: %d %d %d\n", $a->(), $a->(), $a->();
printf "b: %d %d\n", $b->(), $b->();
printf "a again: %d\n", $a->();a: 5 6 7
b: 100 101
a again: 8Three facts in that output, and all three matter.
The captured variable persists between calls. $a returned 5, then 6, then 7. $n was a lexical inside make_counter, whose scope ended when the function returned — and the subroutine still has it, because it still refers to it.
Each closure gets its own copy. $b started at 100 and is entirely independent of $a. Two calls to make_counter created two separate $n variables.
And $a resumed at 8 after $b had been used, confirming they never shared anything.
5. Calling Something That Is Not Code
Not a CODE reference at -e line 1.255The same message family as Chapter 7.3 §7's wrong-kind dereferences, and fatal for the same reason. If a dispatch table might hold something that is not a handler, check it:
my $fn = $handler{$op};
if (ref $fn ne "CODE") {
warn "no usable handler for '$op'\n";
next;
}
$fn->(@args);ref $fn ne "CODE" covers both "absent" and "present but wrong", which a bare exists check does not. Chapter 7.14 is the chapter on ref as a validation tool.
6. Why This Matters in Engineering Work
Command processors are dispatch tables. A script that reads a command file, a REPL, a regression runner that supports a set of actions — all of them are a table lookup plus a call, and all of them get longer and more fragile as elsif chains.
Per-format handlers keep parsers honest. A tool that must read three report formats is naturally %parser = (sta => sub {...}, drc => sub {...}, power => sub {...}). Adding a fourth format touches one place.
Startup validation is the quiet win. Checking keys %handler against the set of operations a configuration actually uses turns a mid-run failure into a refusal to start. That check is only possible because the table is data.
And closures give you configured behaviour. A comparator that sorts by a field chosen at run time, a formatter that knows a width decided earlier, a counter per block — each is a small function that remembers one thing, which is often cleaner than threading that thing through every call.
7. Common Mistakes
\greet instead of \&greet. The first calls the subroutine and takes a reference to its result.
Forgetting the parentheses. $fn is the reference; $fn->() calls it.
|| for the fallback. Use //, so a deliberately falsy entry survives.
exists $handler{$op} as the only check. It does not catch a key holding something that is not code. Test ref $fn eq "CODE".
Calling a non-code reference. Not a CODE reference, fatal.
Closing over a C-style for or while counter. All the closures share it and see the final value. Copy into a my inside the body. (foreach is safe.)
Expecting a closure to capture a value. It captures the variable, and the variable can keep changing.
8. Exercise
Exercise 1 — both forms. Take a reference to a named subroutine with \&name and build an anonymous one with sub { }. Print ref() of each and call both. Then call one with &$ref(...) and confirm it agrees.
Exercise 2 — the wrong backslash. Write my $r = \greet; for a subroutine that returns a string. Print ref($r) and explain in one sentence what it refers to.
Exercise 3 — a dispatch table. Build §2's three handlers and the four-command list including the unknown one. Print each result and the handler count. Confirm the three handlers take zero, one and two arguments respectively.
Exercise 4 — the fallback operator. Replace // with || in the lookup and confirm the output is unchanged. Then add an entry noop => 0 to the table, dispatch noop with both operators, and report the difference.
Exercise 5 — validate the table. With a required list of four operations and a table of three, print the required, defined, missing and extra sets, and die if anything is missing. Record STDERR and the exit status.
Exercise 6 — closures. Build two counters from §4 with different starting values. Interleave calls between them and confirm each keeps its own count. Report the exact sequence you got.
Exercise 7 — which loop traps you. Build three closures four ways and print each set of results: for my $i (1..3); a pre-declared my $i with for $i (1..3); a C-style for (my $i=0; $i<3; $i++); and that same C-style loop with my $c = $i; copied inside the body. Report all four outputs and say which two agree with each other and why.
Exercise 8 — a non-code handler. Put an array reference in a dispatch table and call it. Record the error and exit status. Then add a ref $fn eq "CODE" guard and confirm the script continues.
9. Summary
\&namereferences an existing subroutine;sub { ... }builds an anonymous one.ref()returnsCODEfor both.$ref->(args)calls it. The older&$ref(args)also works.- Arguments and returns are ordinary —
$_[0]behaves exactly as in a named subroutine. - A dispatch table is a hash of handlers, and the whole dispatch is a lookup plus a call.
- Handlers may take different numbers of arguments;
@argsflows through. - Use
//for the fallback, so an intentionally falsy entry is not replaced. - Because the table is data you can validate it against the operations you require, and refuse to start when one is missing.
- A closure captures the variable and keeps it alive. Each closure gets its own copy, and the value persists between calls.
foreachis safe either way — it aliases a fresh loop variable per iteration. A C-styleforor awhileshares one counter, and all the closures see its final value (3 3 3); copy into amyinside the body.- Calling a non-code reference is fatal:
Not a CODE reference. Guard withref $fn eq "CODE".
Chapter 7.12 steps back from building structures to looking at them, using the module that is already written for the job rather than the walker from Chapter 7.10.
Where this fits
Part of the Perl curriculum.
