Build a Lexically Scoped Perl Pragma with %^H
Most Perl pragmas look like magic switches until you build one yourself, and perlpragma is the manual page that shows how the switch actually works. You will build a small user pragma that flips a class's overloaded addition between ordinary floating-point arithmetic and integer arithmetic, following Perl's lexical scope so that no myint restores the surrounding behaviour automatically. Allow about 20 minutes for the example and its checks. The commands use Perl 5.38.2 and perl-doc 5.38.2-3.2ubuntu0.6 as installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
No elevated privileges needed. Work in a scratch directory and keep the three example files together.
Warning
A user pragma is executable code loaded by Perl, so do not run source you have not inspected yourself.
1. Check the Perl version
The installed manual describes user pragmata as modules that affect compile-time or run-time behaviour within a lexical scope. Confirm the interpreter and documentation package before you trust the examples that follow:
$ perl -v
This is perl 5, version 38, subversion 2 ...
$ dpkg-query -W -f='${Package} ${Version}\n' perl-base perl-doc
perl-base 5.38.2-3.2ubuntu0.6
perl-doc 5.38.2-3.2ubuntu0.6
The full perl -v output includes build details that do not matter here. The version itself matters because the manual page documents the syntax and implementation shipped with this particular Perl release.
2. Write the pragma module
Create a directory such as ~/perl-pragma-check, then save this as myint.pm inside it:
package myint;
use v5.36;
sub import {
$^H{"myint/in_effect"} = 1;
}
sub unimport {
$^H{"myint/in_effect"} = 0;
}
sub in_effect {
my $level = shift // 0;
my $hinthash = (caller($level))[10];
return $hinthash->{"myint/in_effect"};
}
1;
use myint; calls import at compile time, and no myint; calls unimport at compile time. Both write a module-specific key into the special %^H hash. The slash in myint/in_effect is deliberate: pragma keys conventionally begin with the main package name followed by /, which keeps them from colliding with other modules doing the same trick.
The in_effect helper reads the hint hash tied to the caller's compiled code. Its optional level argument lets the caller skip one stack frame, which matters when the helper is called from inside an overloaded operator rather than directly.
3. Keep module loading separate from activation
Write this as MyMaths.pm in the same directory:
package MyMaths;
use v5.36;
use myint();
use overload '+' => sub {
my ($l, $r) = @_;
if (myint::in_effect(1)) {
return int($$l) + int($$r);
}
return $$l + $$r;
};
sub new {
my ($class, $value) = @_;
bless \$value, $class;
}
1;
The empty parentheses in use myint(); matter more than they look like they should. They load the module without calling its import method, so merely loading MyMaths does not silently switch on integer arithmetic in the caller's file. The overloaded operator checks the lexical hint belonging to its own caller instead.
4. Exercise the lexical boundaries
Save this as check.pl. The use lib path must point at the directory containing the two modules:
use lib '/home/USER/perl-pragma-check';
use MyMaths;
my $l = MyMaths->new(1.2);
my $r = MyMaths->new(3.4);
print "A: ", $l + $r, "\n";
use myint;
print "B: ", $l + $r, "\n";
{
no myint;
print "C: ", $l + $r, "\n";
}
print "D: ", $l + $r, "\n";
no myint;
print "E: ", $l + $r, "\n";
Replace /home/USER/perl-pragma-check with the real directory. Run it without sudo:
$ perl check.pl
A: 4.6
B: 4
C: 4.6
D: 4
E: 4.6
Checkpoint
The output must alternate exactly as shown. The first addition uses the default behaviour, the later use myint affects code compiled after it, the nested no restores the outer state inside its own block, and the final no disables the pragma for the rest of the file.
5. Understand the compile-time boundary
Pragmata are not ordinary run-time switches. Perl effectively expands use myint; into a compile-time require myint followed by myint->import(), and expands no myint; into the matching unimport call. That is why the lexical state attaches to the optree, the compiled representation of the program, rather than living in one global variable somewhere.
Do not swap this for a package global just because the feature happens to be lexical elsewhere in your code. A global flag makes one scope's choice visible to unrelated code and needs manual cleanup on every path out. The %^H approach gives nested blocks Perl's normal scope rules for free.
6. Store only safe hint values
The hint state lives in the optree and can outlive the interpreter thread that created it. The documented representation supports integers, strings and undef; references and floating-point values get stringified. If you need several values or a more complex structure, serialise it, for example with pack, and decode it again on read.
Warning
Do not hide a reference inside an integer and reconstruct it later. That creates unsynchronised access to Perl data and is not thread-safe. If a setting can be absent, remember that deleting a key and storing undef are different states, so use exists when that distinction actually matters.
7. Diagnose the common mistakes
- Perl cannot locate myint.pm: check
perl -I/path/to/dir check.plor correct theuse libpath. The file name must match the package Perl expects. - Integer mode is active from the start: inspect every module load.
use myint;activates the pragma,use myint();only loads it. - A nested block does not restore its parent state: verify
no myintsits inside the block and that the operator callsin_effect(1). The level argument accounts for the helper's own call frame. - Results go wrong after changing the module: rerun the script in a fresh Perl process. Module code compiles when it loads, and a long-running process may keep the old copy around.
This example changes only files in your scratch directory. To undo it, stop the running script and remove that directory once you have checked it holds nothing else you need. Do not delete a shared Perl library directory as a shortcut.
Done means
- Pragma defined:
myint.pmdefinesimport,unimportand a caller-based state reader. - Loading separated from activation:
MyMaths.pmloads the pragma with an empty import list. - Output matches:
perl check.plprints4.6,4,4.6,4,4.6for A through E. - Hint values are safe: the hint key is namespaced and stores simple, thread-safe values rather than references.