perl.gg / hidden-gems

Do Operator

2024-07-06

A COMPREHENSIVE GUIDE TO PERL'S 'do' OPERATOR

The 'do' operator is one of Perl's most versatile yet often overlooked features. It does different things depending on context - loading files, creating blocks, and enabling some clever control flow tricks.

Let's explore all its faces.

Part 1: LOADING SUBROUTINES FROM FILES

The most common use of 'do' is loading Perl code from external files. Unlike 'require' or 'use', 'do' doesn't check if the file was already loaded - it just runs it.

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
Why the trailing 1; in the file? When 'do' executes a file, it 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.

Error handling best practices:

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's a powerful 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: no trailing 1; 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 is fantastic 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 - 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's 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 - similar to 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 can be combined for powerful 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 is a Swiss Army knife 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
Master these patterns and you'll write cleaner, more flexible Perl code. The 'do' operator might not be flashy, but it's incredibly useful once you know its tricks.

perl.gg