◈ Netsurfer / perl.gg / Do Operator

Do Operator

2024-07-06

This page walks through Perl's do operator and shows how it handles file loading, inline blocks, conditional execution, and loops.

Part 1: LOADING SUBROUTINES FROM FILES

The most common use of do is loading Perl code from external files. Unlike require or use, do skips the loaded-file cache and runs the file every time.

Create a file called 'utils.pl':

# utils.pl sub greet { my ($name) = @_; return "Hello, $name!"; } sub add { my ($a, $b) = @_; return $a + $b; } 1; # Important! Files must end with a true value
Now load and use it:
do "./utils.pl" or die "Cannot load utils.pl: $!"; print greet("World"), "\n"; # Hello, World! print add(5, 3), "\n"; # 8
The trailing 1; in the file matters because do returns the value of the last expression. If that value is false (like 0, "", or undef), your or die check would trigger even though the file loaded successfully.

A safer error handling pattern looks like this:

my $result = do "./config.pl"; if ($@) { die "Syntax error in config.pl: $@"; } elsif (!defined $result) { die "Could not read config.pl: $!"; } elsif (!$result) { die "config.pl did not return a true value"; }

Part 2: LOADING ANONYMOUS FUNCTIONS

Here is a useful pattern: files that return anonymous subroutine references. This lets you load behavior dynamically.

Create 'formatter.pl':

# formatter.pl - Returns an anonymous sub sub { my ($text) = @_; return uc($text) . "!!!"; }
Notice that no trailing 1; is needed here. The anonymous sub itself is the return value, and a code reference is always true.

Load and use it:

my $format = do "./formatter.pl" or die $!; print $format->("hello"); # HELLO!!!
This pattern works well for plugin systems:
# Load all formatters from a directory my %formatters; for my $file (glob("./formatters/*.pl")) { my ($name) = $file =~ /(\w+)\.pl$/; $formatters{$name} = do $file or die "Error loading $file: $!"; } # Use them print $formatters{uppercase}->("test"); print $formatters{reverse}->("test");

Part 3: THE do {} BLOCK

do followed by a block executes the block and returns its last expression. This is useful for inline computations and creating lexical scopes.

Basic usage:

my $result = do { my $temp = 5; $temp * $temp; }; print $result; # 25 # $temp is not accessible here - it was lexically scoped
This is great for complex initializations:
my $config = do { my %conf; $conf{debug} = $ENV{DEBUG} // 0; $conf{port} = $ENV{PORT} // 8080; $conf{host} = $ENV{HOST} // "localhost"; $conf{url} = "http://$conf{host}:$conf{port}"; \%conf; }; print $config->{url}; # http://localhost:8080

Part 4: CONDITIONAL EXECUTION WITH do-if

You can combine do with if for conditional block execution:
my $message = do { if ($age < 13) { "child"; } elsif ($age < 20) { "teenager"; } else { "adult"; } };
This is cleaner than nested ternaries for complex conditions, and the result can be assigned directly.

Another pattern handles optional file loading:

do "./local_config.pl" if -e "./local_config.pl";
This loads the config file only if it exists, without dying if it is missing.

Part 5: LOOPS WITH do-while AND do-until

Unlike regular while loops, do-while executes at least once:
my $count = 0; do { print "Count: $count\n"; $count++; } while ($count < 5);
This guarantees the block runs before checking the condition. Compare to a regular while:
my $count = 10; # This prints nothing - condition checked first while ($count < 5) { print "Count: $count\n"; $count++; } # This prints once - block runs first do { print "Count: $count\n"; $count++; } while ($count < 5);
The do-until variant is also available:
my $input; do { print "Enter 'quit' to exit: "; $input = <STDIN>; chomp($input); } until ($input eq 'quit');

Part 6: INLINE SUBROUTINES WITH do

You can use do {} to create inline code that captures surrounding variables. This behaves like immediately-invoked function expressions (IIFE) in JavaScript:
my @numbers = (1, 2, 3, 4, 5); my $stats = do { my $sum = 0; my $count = 0; for my $n (@numbers) { $sum += $n; $count++; } { sum => $sum, count => $count, average => $sum / $count, }; }; print "Average: $stats->{average}\n"; # Average: 3
This keeps helper variables ($sum, $count) from polluting the outer scope while still producing a useful result.

Part 7: COMBINING PATTERNS

These patterns combine for stronger effects:

Loading config with defaults:

my $config = do { my $loaded = do "./config.pl"; # Merge with defaults { debug => 0, verbose => 0, max_size => 1024, %{ $loaded // {} }, # Override with file settings }; };
Conditional function loading:
my $processor = do { if ($ENV{USE_FAST}) { do "./fast_processor.pl"; } else { do "./standard_processor.pl"; } } or die "Cannot load processor: $!"; $processor->($data);

Part 8: KEY POINTS TO REMEMBER

Files loaded with do:
- Must end with 1; (or any true value) - Unless they return an anonymous sub (which is true) - Are executed every time (unlike require) - Don't update %INC
Anonymous function files:
- Don't need trailing 1; - The sub reference itself is the return value - Perfect for plugins and dynamic loading
do {} blocks:
- Create a lexical scope - Return the last expression - Great for complex initializations
do-while/until loops:
- Execute at least once - Condition checked after each iteration

Conclusion

The do operator covers several distinct jobs in Perl:
do $file - Execute and return file contents do { ... } - Execute block, return last value do { } while () - Loop at least once do { } if () - Conditional block execution
These patterns keep your code cleaner and more flexible. The operator is not flashy, but it is useful once you know how each form behaves.

perl.gg

✓ Document: Donehidden-gems / 11.0 KiB▧