Beautiful Perl series
This post is part of the beautiful Perl features series.
See the introduction post for general explanations about the series.
The last post was about BLOCKs and lexical scoping, a feature present in almost all programming languages. By contrast, today's topic is about another feature quite unique to Perl : dynamic scoping, introduced through the 'local' keyword. As we shall see, lexical scoping and dynamic scoping address different needs, and Perl allows us to choose the scoping strategy most appropriate to each situation.
Using local : an appetizer
Let us start with a very simple example:
say "current time here is ", scalar localtime;
foreach my $zone ("Asia/Tokyo", "Africa/Nairobi", "Europe/Athens") {
local $ENV{TZ} = $zone;
say "in $zone, time is ", scalar localtime;
}
say "but here current time is still ", scalar localtime;
Perl's localtime builtin function returns the time "in the current timezone". On Unix-like systems, the notion of "current timezone" can be controlled through the TZ environment variable, so here the program temporarily changes that variable to get the local time in various cities around the world. At the end of the program, we get back to the usual local time. Here is the output:
current time here is Sun Feb 8 22:21:41 2026
in Asia/Tokyo, time is Mon Feb 9 06:21:41 2026
in Africa/Nairobi, time is Mon Feb 9 00:21:41 2026
in Europe/Athens, time is Sun Feb 8 23:21:41 2026
but here current time is still Sun Feb 8 22:21:41 2026
The gist of that example is that:
- we want to call some code not written by us (here: the
localtimebuiltin function); - the API of that code does not offer any parameter or option to tune its behaviour according to our needs; instead, it relies on some information in the global state of the running program (here: the local timezone);
- apart from getting our specific result, we don't want the global state to be durably altered, because this could lead to unwanted side-effects.
Use cases similar to this one are very common, not only in Perl but in every programming language. There is always some kind of implicit global state that controls how the program behaves internally and how it interacts with its operating system: for example environment variables, command-line arguments, open sockets, signal handlers, etc. Each language has its strategies for dealing with the global state, but often the solutions are multi-faceted and domain-specific. Perl shines because its local mechanism covers a very wide range of situations in a consistent way and is extremely simple to activate.
Before going into the details, let us address the bad reputation of dynamic scoping. This mechanism is often described as something that should be absolutely avoided because it implements a kind of action at distance, yielding unpredictable program behaviour. Yes, it is totally true, and therefore languages that only have dynamic scoping are not suited for programming-in-the-large. Yet in specific contexts, dynamic scoping is acceptable or even appropriate. The most notable examples are Unix shells and also Microsoft Powershell for Windows, that still today all rely on dynamic scoping, probably because it's easier to implement.
Perl1 also used dynamic scoping in its initial design, presumably for the same reason of easiness of implementation, and also because it was heavily influenced by shell programming and was addressing the same needs. When later versions of Perl evolved into a general-purpose language, the additional mechanism of lexical scoping was introduced because it was indispensable for larger architectures. Nevertheless, dynamic scoping was not removed, partly for historical reasons, but also because action at distance is precisely what you want in some specific situations. The next chapters will show you why.
The mechanics of local
In the last post we saw that a my declaration declares a new variable, possibly shadowing a previous variable of the same name. By contrast, a local declaration is only possible on a global variable that already exists; the effect of the declaration is to temporarily push aside the current value of that global variable, leaving room for a new value. After that operation, any code anywhere in the program that accesses the global variable will see the new value ... until the effect of local is reverted, namely when exiting the scope in which the local declaration started.
Here is a very simple example, again based on the %ENV global hash. This hash is automatically populated with a copy of the shell environment when perl starts up. We'll suppose that the initial value of $ENV{USERNAME}, as transmitted from the shell, is Alex:
sub greet_user ($salute) {
say "$salute, $ENV{USERNAME}";
}
greet_user("Hello"); # Hello, Alex
{ local $ENV{USERNAME} = 'Brigitte';
greet_user("Hi"); # Hi, Brigitte
{ local $ENV{USERNAME} = 'Charles';
greet_user("Good morning"); # Good morning, Charles
}
greet_user("Still there"); # Still there, Brigitte
}
greet_user("Good bye"); # Good bye, Alex
The same thing would work with a global package variable:
our $username = 'Alex'; # "our" declares a global package variable
sub greet_user ($salute) {
say "$salute, $username";
}
greet_user("Hello");
{ local $username = 'Brigitte';
greet_user("Hi");
... etc.
but if the variable is not pre-declared, we get an error:
Global symbol "$username" requires explicit package name (did you forget to declare "my $username"?)
or if the variable is pre-declared as lexical through my $username instead of our $username, we get another error:
Can't localize lexical variable $username
So the local mechanism only applies to global variables. There are three categories of such variables:
- standard variables of the Perl interpreter;
- package members (of modules imported from CPAN, or of your own modules). This includes the whole symbol tables of those packages, i.e. not only the global variables, but also the subroutines or methods;
- what the Perl documentation calls elements of composite types, i.e. individual members of arrays or hashes. Such elements can be localized even if the array or hash is itself a lexical variable, because while the entry point to the array or hash may be on the execution stack, its member elements are always stored in global heap memory. This last category of global variables is perhaps more difficult to understand, but we will give examples later to make it clear.
As we can see, the single and simple mechanism of dynamic scoping covers a vast range of applications! Let us explore the various use cases.
Localizing standard Perl variables
The Perl interpreter has a number of builtin special variables, listed in perlvar. Some of them control the internal behaviour of the interpreter; some other are interfaces to the operating system (environment variables, signal handlers, etc.). These variables have reasonable default values that satisfy most common needs, but they can be changed whenever needed. If the change is for the whole program, a regular assignment instruction is good enough; but if it is for a temporary change in a specific context, localis the perfect tool for the job.
Internal variables of the interpreter
The examples below are typical of idiomatic Perl programming: altering global variables so that builtin functions behave differently from the default.
# input record separator ($/)
my @lines = <STDIN>; # regular mode, separating by newlines
my @paragraphs = do {local $/ = ""; <STDIN>}; # paragraph mode, separating by 2 or more newlines
my $whole_content = do {local $/; <STDIN>}; # slurp mode, no separation
# output field and output record separators
sub write_csv_file ($rows, $filename) {
open my $fh, ">:unix", $filename or die "cannot write into $filename: $!";
local $, = ","; # output field separator -- inserted between columns
local $\ = "\r\n"; # output row separator -- inserted between rows
print $fh @$_ foreach @$rows; # assuming that each member of @$rows is an arrayref
}
# list separator in interpolated strings
my @perl_files = <*.pl>; # globbing perl files in the current directory
my @python_files = <*.py>; # idem for python files
{ local $" = ", "; # lists will be comma-separated when interpolated in a string
say "I found these perl files: @perl_files and these python files: @python_files";
}
Interface to the operating system
We have already seen two examples involving the %ENV hash of environment variables inherited from the operating system. Likewise, it is possible to tweak the @ARGV array before parsing the command-line arguments. Another interesting variable is the %SIG hash of signal handlers, as documented in perlipc:
{ local $SIG{HUP} = "IGNORE"; # don't want to be disturbed for a while
do_some_tricky_computation();
}
Predefined handles like STDIN, STDOUT and STDERR can also be localized:
say "printing to regular STDOUT";
{ local *STDOUT;
open STDOUT, ">", "captured_stdout.txt" or die $!;
do_some_verbose_computation();
}
say "back to regular STDOUT";
Localizing package members
Package global variables
Global variables are declared with the our keyword. The difference with lexical variables (declared with my) is that such global variables are accessible, not only from within the package, but also from the outside, if prefixed by the package name. So if package Foo::Bar declares our ($x, @a, %h), these variables are accessible from anywhere in the Perl program under $Foo::Bar::x, @Foo::Bar::a or %Foo::Bar::h.
Many CPAN modules use such global variables to expose their public API. For example, the venerable Data::Dumper chooses among various styles for dumping a data structure, depending on the $Data::Dumper::Indent variable. The default (style 2) is optimized for readability, but sometimes the compact style 0 is more appropriate:
print Dumper($data_tree);
print do {local $Data::Dumper::Indent=0; Dumper($other_tree)};
Carp or URI are other examples of well-known modules where global variables are used as a configuration API.
Modules with this kind of architecture are often pretty old; more recent modules tend to prefer an object-oriented style, where the configuration options are given as options to the new() method instead of using global variables. Of course the object-oriented architecture offers better encapsulation, since a large program can work with several instances of the same module, each instance having its own configuration options, without interference between them. This is not to say, however, that object-oriented configuration is always the best solution: when it comes to tracing, debugging or profiling needs, it is often very convenient to be able to tune a global knob and have its effect applied to the whole program: in those situations, what you want is just the opposite of strict encapsulation! Therefore some modules, although written in a modern style, still made the wise choice of leaving some options expressed as global variables; changing these options has a global effect, but thanks to local this effect can be limited to a specific scope. Examples can be found in Type::Tiny or in List::Util.
Subroutines (aka monkey-patching)
Every package has a symbol table that contains not only its global variables, but also the subroutines (or methods) declared in that package. Since the symbol table is writeable, it is possible to overwrite any subroutine, thereby changing the behaviour of the package - an operation called monkey-patching. Of course monkey-patching could easily create chaos, so it should be used with care - but in some circumstances it is extremely powerful and practical. In particular, testing frameworks often use monkey-patching for mocking interactions with the outside world, so that the internal behaviour of a sofware component can be tested in isolation.
The following example is not very realistic, but it's the best I could come up with to convey the idea in just a few lines of code. Consider a big application equipped with a logger object. Here we will use a logger from Log::Dispatch:
use Log::Dispatch;
my $log = Log::Dispatch->new(
outputs => [['Screen', min_level => 'info', newline => 1]],
);
The logger has methods debug(), info(), error(), etc. for accepting messages at different levels. Here it is configured to only log messages starting from level info; so when the client code calls info(), the message is printed, while calls to debug() are ignored. As a result, when the following routine is called, we normally only see the messages "start working ..." and "end working ...":
sub work ($phase) {
$log->info("start working on $phase");
$log->debug("weird condition while doing $phase"); # normally not seen - level below 'info'
$log->info("end working on $phase\n");
}
Now suppose that we don't want to change the log level for the whole application, but nevertheless we need to see the debug messages at a given point of execution. One (dirty!) way of achieving this is to temporarily treat calls to debug() as if they were calls to info(). So a scenario like
work("initial setup");
{ local *Log::Dispatch::debug = *Log::Dispatch::info; # temporarily alias the 'debug' method to 'info'
work("core stuff")}
work("cleanup");
logs the following sequence:
start working on initial setup
end working on initial setup
start working on core stuff
weird condition while doing core stuff
end working on core stuff
start working on cleanup
end working on cleanup
Monkey-patching techniques are not specific to Perl; they are used in all dynamic languages (Python, Javascript, etc.), not for regular programming needs, but rather for testing or profiling tasks. However, since other dynamic languages do not have the local mechanism, temporary changes to the symbol table must be programmed by hand, by storing the initial code reference in a temporary variable, and restoring it when exiting from the monkey-patched scope. This is a bit more work and is more error-prone. Often there are library modules for making the job easier, though: see for example https://docs.pytest.org/en/7.1.x/how-to/monkeypatch.html in Python.
Monkey-patching in a statically-typed language like Java is more acrobatic, as shown in Nicolas Fränkel's blog.
Localizing elements of arrays or hashes
The value of an array at a specific index, or the value of a hash for a specific key, can be localized too. We have already seen some examples with the builtin hashes %ENV or %SIG, but it works as well on user data, even when the data structure is several levels deep. A typical use case for this is when a Web application loads a JSON, YAML or XML config file at startup. The config data becomes a nested tree in memory; if for any reason that config data must be changed at some point, local can override a specific data leaf, or any intermediate subtree, like this:
local $app->config->{logger_file} = '/opt/log/special.log';
local $app->config->{session} = {storage => '/opt/data/app_session',
expires => 42900,
};
Another example can be seen in my Data::Domain module. The job of that module is to walk through a datatree and check if it meets the conditions expected by a "domain". The inspect() method that does the checking sometimes needs to know at which node it is currently located; so a $context tree is worked upon during the traversal and passed to every method call. With the help of local, temporary changes to the shape of $context are very easy to implement:
for (my $i = 0; $i < $n_items; $i++) {
local $context->{path} = [@{$context->{path}}, $i];
...
An alternative could have been to just push $i on top of the @{$context->{path}} array, and then pop it back at the end of the block. But since this code may call subclasses written by other people, with little guarantee on how they would behave with respect to $context->{path}, it is safer to localize it and be sure that $context->{path} is in a clean state when starting the next iteration of the loop. Interested readers can skim through the source code to see the full story. A similar technique can also be observed in Catalyst::Dispatcher.
A final example is the well-known DBI module, whose rich API exploits several Perl mechanisms simultaneously. DBI is principally object-oriented, except that the objects are called "handles"; but in addition to methods, handles also have "attributes" accessible as hash members. The DBI documentation explicitly recommends to use local for temporary modifications to the values of attributes, for example for the RaiseError attribute. This is interesting because it shows a dichotomy between API styles: if DBI had a purely object-oriented style, with usual getter and setter methods, it would be impossible to use the benefits of local - temporary changes to an attribute followed by a revert to the previous value would have to be programmed by hand.
How other languages handle temporary changes to global state
As argued earlier, the need for temporary changes to global state occurs in every programming language, in particular for testing, tracing or profiling tasks. When dynamic scoping is not present, the most common solution is to write specific code for storing the old state in a temporary variable, implement the change for the duration of a given computation, and then restore the old state. A common best practice for such situations is to use a try ... finally ... construct, where restoration to the old state is implemented in the finally clause: this guarantees that even if exceptions occur, the code exits with a clean state. Most languages do possess such constructs - this is definitely the case for Java, JavaScript and Python.
Python context managers
Python has a mechanism more specifically targeted at temporary changes of context : this is called context manager, triggered through a with statement. A context manager implements special methods __enter__() and __exit__() that can be programmed to operate changes into the global context. This technique offers more precision than Perl's local construct, since the enter and exit methods are free to implement any kind of computation; however it is less general, because each context manager is specialized for a specific task. Python's contextlib library provides a collection of context managers for common needs.
Wrapping up
Perl's local mechanism is often misunderstood. It is frowned upon because it breaks encapsulation - and this criticism is perfectly legitimate as far as regular programming tasks are concerned; but on the other hand, it is a powerful and clean mechanism for temporary changes to the global execution state. It solves real problems with surprising grace, and it’s one of the features that makes Perl uniquely expressive among modern languages. So do not follow the common advice to avoid local at all costs, but learn to identify the situations where local will be a helpful tool to you!
Beyond the mere mechanism, the question is more at the level of philosophy of programming: to which extent should we enforce strict encapsulation of components? In an ideal world, each component has a well-defined interface, and interactions are only allowed to go through the official interfaces. But in a big assembly of components, we may encounter situations that were not foreseen by the designers of the individual components, and require inserting some additional screws, or drilling some additional holes, so that the global assembly works satisfactorily. This is where Perl's local is a beautiful device.
About the cover picture
The cover picture1 represents a violin with a modified global state: the two intermediate strings are crossed! This is the most spectacular example of scordatura in Heirich Biber's Rosary sonatas (sometimes also called "Mystery sonatas"). Each sonata in the collection requires to tune the violin in a specific way, different from the standard tuning in fifths, resulting in very different atmospheres. It requires some intellectual gymnastics from the player, because the notes written in the score no longer represent the actual sounds heard, but merely refer to the locations of fingers on a normal violin; in other words, this operation is like monkey-patching a violin!
Top comments (0)