Perl subroutines, context, and closures
Return different results in list and scalar context, build closures, and write a recursive anonymous function.
use strict;
use warnings;
sub minmax {
my @sorted = sort { $a <=> $b } @_;
return wantarray ? ($sorted[0], $sorted[-1]) : "$sorted[0]..$sorted[-1]";
}
my ($min, $max) = minmax(4, 9, 2, 7);
my $range = minmax(4, 9, 2, 7);
print "$min $max $range\n";
sub counter {
my $n = shift // 0;
return { inc => sub { return ++$n }, get => sub { return $n } };
}
my $c1 = counter();
my $c2 = counter(10);
$c1->{inc}->() for 1 .. 3;
$c2->{inc}->();
print $c1->{get}->(), " ", $c2->{get}->(), "\n";
my $factorial;
$factorial = sub {
my $n = shift;
return $n <= 1 ? 1 : $n * $factorial->($n - 1);
};
print $factorial->(5), "\n";
my @list = (3, 1, 2);
my $count = @list;
my ($first) = @list;
print "$count $first\n";2 9 2..9 3 11 120 3 3
Explanation
Perl subroutines receive their arguments in the array @_ and usually copy them with my. Context is central to Perl: the same expression can behave differently in list and scalar context. A subroutine can ask for its caller's context with wantarray and return a list in one case and a single value in the other. An array in scalar context is its length, while assigned to a list it gives its elements.
A closure is an anonymous subroutine that remembers the my variables that were in scope when it was created. counter() below returns two closures that share one $n, and every call to counter() creates a separate $n. An anonymous subroutine can call itself through a variable declared beforehand.
Explore the dry run
use strict;use warnings; sub minmax { my @sorted = sort { $a <=> $b } @_; return wantarray ? ($sorted[0], $sorted[-1]) : "$sorted[0]..$sorted[-1]";}my ($min, $max) = minmax(4, 9, 2, 7);my $range = minmax(4, 9, 2, 7);print "$min $max $range\n"; sub counter { my $n = shift // 0; return { inc => sub { return ++$n }, get => sub { return $n } };}my $c1 = counter();my $c2 = counter(10);$c1->{inc}->() for 1 .. 3;$c2->{inc}->();print $c1->{get}->(), " ", $c2->{get}->(), "\n"; my $factorial;$factorial = sub { my $n = shift; return $n <= 1 ? 1 : $n * $factorial->($n - 1);};print $factorial->(5), "\n"; my @list = (3, 1, 2);my $count = @list;my ($first) = @list;print "$count $first\n";Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| wantarray is true in list context. The subroutine returns a list there and a string in scalar context. | — | — | |
| Assigning to a list of variables gives the call list context. | — | — | |
| Assigning to one scalar gives the call scalar context. | — | — | |
| The same call produced different results. | — | 2 9 2..9 | |
| Two closures that share the private variable $n. | — | 2 9 2..9 | |
| The first counter is incremented three times, the second once. | — | 2 9 2..9 | |
| The counters do not affect each other. | — | 2 9 2..9 3 11 | |
| The anonymous subroutine calls itself through $factorial: 5·4·3·2·1. | — | 2 9 2..9 3 11 120 | |
| An array in scalar context is its length. | — | 2 9 2..9 3 11 120 | |
| my ($first) = @list takes only the first element, the parentheses make it a list assignment. | — | 2 9 2..9 3 11 120 3 3 |
Cost and common mistakes
Each call is O(1) except minmax, which sorts in O(n log n).
- my $first = @list gives the length, not the first element. The parentheses in my ($first) = @list change the context.
- Declare $factorial before assigning the closure, or the name is not yet visible inside the anonymous subroutine.
- Modifying @_ elements directly changes the caller's variables because @_ aliases them. Copy with my first.
Try it yourself
What does print scalar(minmax(1, 2)) print?
Reveal answer and reasoning
1..2: scalar() forces scalar context, so minmax returns the string.