Замкнення (closure) - це анонімна підпограма, згенерована під час виконання, що пам'ятає про лексичний контекст, в якому вона була задана. Це визначення може видатися дещо незрозумілим, тому для початку розгляньмо простенький приклад, де показано, як приватні змінні можуть зберігати свої значення при виході з контексту, де вони були задані:
my $scalarref;
{
my $private = 33;
$scalarref = \$private;
}
print $$scalarref; # 33
Виконання цього коду виведе "33". Що ж тут відбувається? Змінна $private задана всередині анонімного блоку. Звичайно при виході з блоку, що визначає лексичний контекст певної змінної, вона відразу ж потрапляє до смітника. Якщо ж створити посилання на цю змінну, то її величину можна отримати і змінити у зовнішньому контексті завдяки посиланню на неї.
Посилатися на приватні змінні можливо і за допомогою підпрограм:
my $count; # глобальна змінна
{
my $count = 0; # змінна, видима всередині блоку
sub counter { ++$count }
}
print &counter, "\n" for (1..3); # 1, 2, 3
$count = 10;
print &counter, "\n"; # 4
З цього прикладу видно, що підпрограма counter утримує інформацію про свій початковий контекст: вона використовує приватну змінну $count, недоступну в глобальному контексті, створюючи ефект "статичної" змінної (за термінологією C/C++). Такі змінні можуть поділятися і між кількома функціями одночасно:
{
my $count = 0;
sub increment { ++$count }
sub decrement { --$count }
sub reset { $count = 0 }
}
Тепер ми підійшли впритул до замкнень:
sub foo {
my $x = shift;
return sub {
my $y = shift;
return $x * $y;
}
}
my $closure = &foo(10);
print $closure->(3); # 30
Підпрограма foo отримує один аргумент і повертає аномімну функцію, яка має доступ до приватних змінних підпрограми, що її згенерувала (foo). Посилання на новоутворене замкнення зберігається в змінній $closure, а виконання самого замкнення викликається за допомогою реферативного оператора ->
, за яким розташовуються дужки.
Замкнення можуть також вкладатися одне в одне:
sub foo {
my $x1 = shift;
return sub {
my $x2 = shift;
return sub {
my $x3 = shift;
return $x1 * $x2 * $x3;
}
}
}
my $cl1 = foo(10);
my $cl2 = $cl1->(2);
print $cl2->(3); # 60
Тут виклик функції foo повертає замкнення, а виклик цього замкнення у свою чергу повертає інше.
Коли ж потрібні замкнення? Насамперед тоді, коли треба створити функцію, що пам'ятає про свій початковий стан, але вам не хочеться створювати новий об'єкт (адже задання замкнення є більш компактним). Особливо ж вони корисні тоді, коли вам потрібно задати кілька подібних функцій з різним початковим станом:
# масив кольорів:
my @colors = qw(red blue navy purple pink);
# функція, що отримує назву кольору як арґумент
# і повертає замкнення,
# котре генерує відповідну html-мітку:
sub colorize {
my $color = shift;
return sub {
my $text = shift;
return qq|<span style="color: $color">$text</span>|;
}
}
# задаємо асоціативний масив, ключами якого є
# назви кольорів, а величинами відповідні замкнення:
my %colors = map { $_ => &colorize($_) } @colors;
# випадковий колір:
my $color = $colors[rand(@colors)];
# виведення повідомлення:
print "Today my world is ", $colors{$color}->($color);
Можливий вивід:
Today my world is <span style="color: blue">blue</span>