Perl Part I · Module 12 · Modules and Packages
A Module With State
Chapter 12.7 established that a my variable at file scope is genuinely private — reachable by the module's own subroutines and by nothing else — and used it for a single setting. This chapter puts the same technique to the use it was really made for: data the module accumulates while it runs. The example is a cache, because caching is the case where the benefit is measurable rather than stylistic, and section one measures it: six requests answered by three computations, with the other three served from memory. The second half is the fact people most often get wrong about module state, and it follows from Chapter 12.4's finding that a module's body runs exactly once however many files load it. There is one copy of that cache for the whole process, shared by every caller in it. That is usually what you want from a cache and emphatically not what you want from anything resembling a per-task record, and knowing which you have written is the difference between a fast script and a wrong one.
Advanced12 min readPerlPart IModules
Part I · Module 12 · Chapter 12.11 · A Module With State
1. A Cache Nobody Can Reach
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
mkdir File::Spec->catdir($dir, "VLSI");
open my $fh, ">", File::Spec->catfile($dir, "VLSI", "Derate.pm") or die $!;
print $fh <<'M';
package VLSI::Derate;
use strict;
use warnings;
# Private to this file. No caller can see it, by name or otherwise.
my %cache;
my $computed = 0;
sub factor {
my ($corner) = @_;
return $cache{$corner} if exists $cache{$corner};
$computed++; # pretend this was expensive
my %table = (slow => 1.10, typ => 1.00, fast => 0.92);
return $cache{$corner} = $table{$corner} // 1.00;
}
sub stats { return { cached => scalar keys %cache, computed => $computed } }
sub reset_cache { %cache = (); $computed = 0; return }
1;
M
close $fh;
unshift @INC, $dir;
require VLSI::Derate;
# Ask for the same corners repeatedly
my @asks = qw(slow typ slow fast typ slow);
printf "asked %d time(s) for: %s\n", scalar @asks, join(" ", @asks);
printf "factors : %s\n", join(" ", map { VLSI::Derate::factor($_) } @asks);
my $s = VLSI::Derate::stats();
printf "distinct cached : %d\n", $s->{cached};
printf "actually computed : %d\n", $s->{computed};
VLSI::Derate::reset_cache();
printf "after reset : cached=%d computed=%d\n",
VLSI::Derate::stats()->{cached}, VLSI::Derate::stats()->{computed};
{
no strict 'refs'; no warnings 'uninitialized';
my @keys = eval { keys %VLSI::Derate::cache };
printf "reachable as \%%VLSI::Derate::cache : %d key(s)\n", scalar @keys;
}asked 6 time(s) for: slow typ slow fast typ slow
factors : 1.1 1 1.1 0.92 1 1.1
distinct cached : 3
actually computed : 3
after reset : cached=0 computed=0
reachable as %VLSI::Derate::cache : 0 key(s)Six requests, three computations. The three repeats were served from the cache, and the factors returned are identical whether a value came from the table or from memory — which is the only correctness property a cache has to have.
The last line is the privacy result. %VLSI::Derate::cache has zero keys, because there is no such package variable: the real cache is a my hash in the module's file scope, visible to factor, stats and reset_cache and to nothing else. A caller cannot inspect it, cannot corrupt it, and cannot accidentally hold a reference into it.
Three design choices in that module are worth naming, because they are the pattern rather than the example:
exists rather than truth for the cache check. return $cache{$corner} if exists $cache{$corner}; works when a cached value is legitimately 0 or the empty string, which Chapter 11.10's measurement showed are both false. A cache tested with if ($cache{$k}) recomputes every zero for ever.
A stats accessor rather than exposing the hash. It returns a small summary, so the module can change how it stores things without breaking a caller. Nothing hands out a reference to %cache itself — that would publish the state as surely as our would.
A reset_cache so a long-running process can clear it. The measurement confirms it works, and the reason to provide it is that a cache with no way to clear it is a memory leak with a polite name.
2. One Copy, For the Whole Process
use strict;
use warnings;
# A module's state lives for the life of the PROCESS, and is shared by every
# caller in it -- there is one copy, not one per importing file.
package Counter {
my $n = 0;
sub bump { return ++$n }
}
package Caller::A { sub go { return Counter::bump() } }
package Caller::B { sub go { return Counter::bump() } }
printf "A: %d\n", Caller::A::go();
printf "B: %d\n", Caller::B::go();
printf "A: %d\n", Caller::A::go();
print "\none counter, shared by both callers -- module state is per PROCESS\n";A: 1
B: 2
A: 3
one counter, shared by both callers -- module state is per PROCESSTwo unrelated caller packages, and the counter went 1, 2, 3 across both of them.
This follows directly from Chapter 12.4's measurement that a second require of the same module is a no-op. The module's body — including my $n = 0; — runs exactly once, the first time anything loads it. Every subsequent use from every other file finds it already in %INC and does nothing. So there is one $n, and everybody who calls bump increments the same one.
3. When State Is the Wrong Choice
The canonical rating for this chapter is Medium, and the honest reason is that module-level state is right less often than it is used.
A cache is the clear case. Expensive and repeatable work, the same answer every time for the same input, and no reason for one caller to want a different answer from another. Derating factors, a parsed configuration file, a library cell lookup.
A single process-wide setting is a reasonable case, which is Chapter 12.7's accessor.
Everything else deserves suspicion. If the state describes a thing — a report, a path, a connection — then there can be two of them, and a module-level variable can hold only one. The next two chapters are about the alternative: a hash reference holding the state, blessed into a package so it carries its own methods, which gives you as many independent copies as you need.
There is also a middle option worth knowing. A closure over a my variable gives you private state with as many copies as you like, without any object syntax:
sub make_counter { my $n = 0; return sub { return ++$n } }
Each call to make_counter returns a new subroutine with its own $n, which Chapter 10.10 measured. That is genuinely private — more private than an object's hash, which any caller can reach into — and it is the right tool when you want independent state and no methods.
4. Two Practical Notes
Initialisation order. A my variable at file scope is initialised when the module's body runs, which is during the require — so at compile time if the caller used use. That is before the caller's own run-time code, which is usually what you want. What you cannot do is depend on another module's state being initialised first, unless that module is loaded first; if two modules initialise from each other at load time you have a dependency on use ordering, which is a problem nobody enjoys.
Calling a subroutine that returns a reference. The error I hit writing this chapter is worth passing on, because the message points nowhere near the cause. Writing VLSI::Derate::stats->{cached} gets you:
Can't use bareword ("VLSI::Derate::stats") as a HASH ref while "strict refs" in use
Perl parsed the name as a bareword string rather than as a call, then tried to use it as a reference. The fix is explicit parentheses — stats()->{cached} — and the general rule is that a fully-qualified subroutine call followed by an arrow needs them. With a method call, $obj->stats->{cached} is fine, because the arrow has already told Perl it is a method.
5. What To Remember
- A
myvariable at file scope is a module's private state, visible to its own subroutines and to nothing else. Measured:0 key(s)reachable as a package variable. - A cache is the case where this pays. Measured: six requests, three computations.
- Check the cache with
exists, not truth — a cached0or""is false and would be recomputed for ever. - Offer a summary accessor, not the data. Handing out a reference to the state publishes it as effectively as
ourwould. - Provide a way to clear it, or a long-running process has a slow leak.
- Module state is per process, not per importing file. Measured: two caller packages shared one counter, 1-2-3, because the module body runs exactly once.
- Ask what happens if two parts of the program use the module for two different things. If the answer is bad, the state belongs in an object.
- A closure gives private state with many copies and no object syntax —
sub make_counter { my $n = 0; return sub { ++$n } }. Pkg::sub->{key}is a bareword error. WritePkg::sub()->{key}.
Chapter 12.12 takes the step this chapter has been pointing at. An object in Perl is a reference — usually to a hash — marked with a package name by bless, and the measurement there shows that marking it changes exactly one thing.
Where this fits
Part of the Perl curriculum.
