◈ Netsurfer / perl.gg / Closures

Closures

2024-07-01

Part 1: WHAT THE HECK IS A CLOSURE?

This page explains what a closure is in Perl and how to use them. A closure is a function that captures its lexical environment, which just means it remembers the variables that existed when it was created. Those variables stay alive even after the outer function finishes running. The code block below shows the standard example.
sub create_greeter { my $name = shift; return sub { say "Hello, $name!"; }; } my $greet_alice = create_greeter('Alice'); my $greet_bob = create_greeter('Bob'); $greet_alice->(); # Hello, Alice! $greet_bob->(); # Hello, Bob!
See what happened there? The anonymous sub we returned "closed over" the $name variable. Even though create_greeter() finished executing (and $name should have been destroyed), the returned subroutine still has access to it.

That's a closure. A function bundled with its environment.

Part 2: THE MECHANICS OF CLOSING OVER

When Perl creates an anonymous subroutine, it captures the variables themselves rather than just their current values. Each variable lives on and stays bound to that specific subroutine. This behavior matters because it gives you state preservation without objects.
sub create_counter { my $count = 0; return sub { return ++$count; }; } my $counter_a = create_counter(); my $counter_b = create_counter(); say $counter_a->(); # 1 say $counter_a->(); # 2 say $counter_a->(); # 3 say $counter_b->(); # 1 (separate instance!) say $counter_b->(); # 2
Each call to create_counter() creates a NEW $count variable, and the returned closure captures THAT specific $count. The two counters are completely independent.

This is state preservation without objects. Private data without classes. Encapsulation through lexical scope.

Part 3: FUNCTION FACTORIES

A function factory is a routine that builds and returns other functions. Closures make this pattern possible.
sub make_multiplier { my $factor = shift; return sub { my $n = shift; return $n * $factor; }; } my $double = make_multiplier(2); my $triple = make_multiplier(3); say $double->(5); # 10 say $triple->(5); # 15 say $double->(10); # 20
The factory pattern is powerful because you parameterize behavior at creation time and then call the result like any other function.

MORE ELABORATE EXAMPLE:

sub make_validator { my %rules = @_; return sub { my $value = shift; if (exists $rules{min} && $value < $rules{min}) { return (0, "Value must be >= $rules{min}"); } if (exists $rules{max} && $value > $rules{max}) { return (0, "Value must be <= $rules{max}"); } if (exists $rules{match} && $value !~ $rules{match}) { return (0, "Value must match $rules{match}"); } return (1, "OK"); }; } my $age_check = make_validator(min => 0, max => 150); my $zip_check = make_validator(match => qr/^\d{5}$/); my ($ok, $msg) = $age_check->(25); # (1, "OK") my ($ok, $msg) = $age_check->(-5); # (0, "Value must be >= 0") my ($ok, $msg) = $zip_check->("abc"); # (0, "Value must match...")

Part 4: LEXICAL SCOPE - THE MAGIC INGREDIENT

Closures only capture lexical variables declared with my. Package variables do not get captured because they live in the global scope.
our $global = "I'm global"; sub bad_closure { return sub { say $global }; } my $fn = bad_closure(); $global = "Changed!"; $fn->(); # "Changed!" - not captured, just accessed globally
But with lexicals:
sub good_closure { my $local = "I'm captured"; return sub { say $local }; } my $fn = good_closure(); # Even if there was a way to change $local from outside # (there isn't), $fn would still see "I'm captured"
This is why Perl's "use strict" is so important. It forces you into lexical scope, which makes closures work properly.

Part 5: FIRST-CLASS FUNCTIONS AND CLOSURES

Closures work because Perl treats functions as first-class values. You can:

STORE THEM:

my $fn = sub { ... };
PASS THEM:
some_function(sub { ... });
RETURN THEM:
return sub { ... };
PUT THEM IN DATA STRUCTURES:
my @funcs = ( sub { say "one" }, sub { say "two" }, sub { say "three" }, ); $_->() for @funcs;
GENERATE THEM DYNAMICALLY:
my @multipliers = map { make_multiplier($_) } 1..10; say $multipliers[4]->(10); # 50 (5 * 10)

Part 6: COMMON GOTCHA - THE LOOP VARIABLE TRAP

This pattern trips up almost everyone at least once.
my @closures; for my $i (1..3) { push @closures, sub { say $i }; } $_->() for @closures; # 1, 2, 3 - works!
Wait, that works? Yes, because Perl 5.10+ creates a fresh $i for each iteration when you use my in the loop declaration.

But watch out for this older pattern:

my @closures; my $i; for $i (1..3) { push @closures, sub { say $i }; } $_->() for @closures; # 3, 3, 3 - oops!
All three closures captured the SAME $i, and by the time we call them, $i is 3.

The fix (for old code or when you can't use loop-scoped my):

for my $i (1..3) { my $captured = $i; # fresh lexical each iteration push @closures, sub { say $captured }; }

Part 7: CLOSURES VS OBJECTS

There's a famous saying: "Objects are a poor man's closures. Closures are a poor man's objects."

Both are mechanisms for bundling data with behavior. Compare:

# Object approach package Counter; sub new { my $class = shift; my $self = { count => 0 }; return bless $self, $class; } sub increment { ++$_[0]->{count} } sub value { $_[0]->{count} } my $c = Counter->new; $c->increment; say $c->value; # Closure approach sub make_counter { my $count = 0; return { increment => sub { ++$count }, value => sub { $count }, }; } my $c = make_counter(); $c->{increment}->(); say $c->{value}->();
The closure version is more concise and arguably more encapsulated. There is no way to access $count except through the provided interface.

Use objects when you need inheritance, introspection, or the full OO toolkit. Use closures when you need lightweight state encapsulation.

Part 8: THE PHILOSOPHICAL BIT

Closures represent a fundamental truth about computation: context matters. A function isn't just its code. It is its code plus the environment in which it was created.

This is why closures feel natural for:

  • Callbacks (they remember what they were called about)
  • Event handlers (they remember their context)
  • Partial application (they remember some arguments)
  • Memoization (they remember previous results)
  • Iterators (they remember where they are)

When you create a closure, you're creating a little world. A self-contained unit of behavior with its own private memories.

That's not just useful. That's beautiful.

"Closures are the poor man's objects, and objects are the poor man's closures." But in Perl, you're never poor. You have both.
perl.gg
✓ Document: Donefunctional / 11.2 KiB▧