Подпрограммы: sub и @_

Представь, что ты пишешь скрипт, который разбирает лог-файл сервера. Тебе нужно раз двадцать проверять, похожа ли строка на дату, и каждый раз копировать одну и ту же проверку — три строки кода. Через полсотни строк скрипт превращается в кашу: одна и та же логика размазана по всему файлу, а если в ней найдётся ошибка, придётся чинить её в двадцати местах. Именно для этого в любом языке программирования придумали подпрограммы (в других языках их чаще называют функциями) — куски кода, которым можно дать имя и вызывать по этому имени сколько угодно раз.

В Perl подпрограмма объявляется ключевым словом sub. Это одна из первых конструкций, которую стоит освоить всерьез, потому что без неё любой скрипт длиннее пары десятков строк становится нечитаемым.

Зачем это нужно в языке

Perl изначально создавался как язык для обработки текста и системного администрирования — скрипты, которые парсят логи, генерируют отчёты, переименовывают тысячи файлов. Такие задачи почти всегда состоят из повторяющихся шагов: «взять строку, проверить её, что-то с ней сделать». Без подпрограмм пришлось бы копировать одну и ту же проверку снова и снова. С подпрограммами ты пишешь логику один раз, даёшь ей понятное имя вроде is_valid_date и просто вызываешь это имя там, где нужно.

Есть и вторая причина: подпрограммы помогают думать о задаче по частям. Вместо одного длинного скрипта на триста строк ты получаешь набор маленьких понятных кусочков, каждый из которых делает одну вещь. Так проще писать код, проще искать ошибки и проще возвращаться к своему же скрипту через полгода.

Синтаксис с примером

Вот простейшая подпрограмма, которая складывает два числа:

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $result = add(5, 3);
print "Сумма: $result\n";

Вывод:

Сумма: 8

Обрати внимание: у подпрограммы add в объявлении нет списка параметров в скобках, как в Python или Java. Это не опечатка и не упрощённый пример — так устроен сам язык.

Разбор построчно

sub add { ... } — объявляет подпрограмму с именем add. Всё, что внутри фигурных скобок, — это её тело, код, который выполнится при вызове.

my ($a, $b) = @_; — самая непривычная строка для новичка, и на ней стоит задержаться. В Perl подпрограмма не описывает параметры в заголовке. Вместо этого все аргументы, с которыми её вызвали, автоматически попадают в специальный массив с именем @_. Когда мы написали add(5, 3), внутри подпрограммы @_ стал равен (5, 3). Строка my ($a, $b) = @_; — это просто раскладывание значений из массива @_ в два обычных именованных переменных, с которыми удобнее работать дальше. Слово my создаёт новую переменную, видимую только внутри этого блока кода.

return $a + $b; — возвращает результат сложения туда, откуда подпрограмму вызвали. Ключевое слово return работает примерно так же, как в большинстве других языков: выполнение подпрограммы сразу останавливается, а указанное значение становится результатом вызова.

my $result = add(5, 3); — вызываем подпрограмму с двумя аргументами и сохраняем то, что она вернула, в переменную $result.

Практический пример

Массив @_ — это обычный массив, поэтому подпрограмма может принимать произвольное число аргументов, а не строго фиксированное. Вот функция, которая находит наибольшее число среди любого количества переданных значений:

sub max_value {
    my @numbers = @_;
    my $max = $numbers[0];
    foreach my $n (@numbers) {
        if ($n > $max) {
            $max = $n;
        }
    }
    return $max;
}

print max_value(3, 7, 2, 9, 4), "\n";
print max_value(15, 1), "\n";

Вывод:

9
15

Здесь max_value вызывается то с пятью числами, то с двумя — и это работает без каких-либо изменений в самой подпрограмме, потому что она просто перебирает всё, что оказалось в @_. Кстати, если внутри подпрограммы явно нет return, Perl вернёт результат последнего вычисленного выражения — но пока лучше всегда писать return явно, так код читается понятнее и меньше шансов на сюрприз.

Частые ошибки

Первая и самая частая ошибка новичков — попытка написать параметры в скобках после имени подпрограммы, как в других языках: sub add($a, $b) { ... }. В классическом Perl (том, который используется в подавляющем большинстве учебных материалов и старого кода) так писать нельзя — Perl либо выдаст ошибку, либо проигнорирует содержимое скобок. Параметры всегда достаются из @_.

Вторая ошибка — забыть слово my при распаковке аргументов. Если написать просто ($a, $b) = @_; без my, код может сработать, но переменные $a и $b окажутся глобальными для всего скрипта, а не локальными для подпрограммы — это источник трудноуловимых багов, когда одна подпрограмма незаметно портит переменные другой.

Третья ошибка — путать количество переданных аргументов с тем, что реально лежит в @_. Если вызвать add(5), передав только одно число вместо двух, Perl не выдаст ошибку сама по себе. Внутри подпрограммы $b просто окажется пустым (Perl называет такое значение undef), а попытка сложить число с undef тихо даст странный результат или предупреждение. Про то, как ловить такие ситуации на раннем этапе, — в следующем уроке про use strict и use warnings.

Итоги

Подпрограмма в Perl объявляется через sub имя { тело }. Аргументы вызова автоматически попадают в специальный массив @_, и его обычно сразу же распаковывают в именованные переменные через my ($a, $b) = @_;. Результат возвращается ключевым словом return. Раз в @_ — обычный массив, подпрограмма может принимать сколько угодно аргументов, что делает функции вроде max_value гибкими без лишнего кода.

Проверьте себя
1. Как подпрограмма в Perl получает аргументы, с которыми её вызвали?
AЧерез список параметров в скобках после имени sub, как в Python
BЧерез специальный массив @_ внутри тела подпрограммы
CЧерез глобальную переменную $args
DАргументы нужно объявлять отдельной директивой use params
2. Что произойдёт, если написать (a, b) = @_; без ключевого слова my?
APerl откажется запускать скрипт
BПеременные окажутся глобальными для всего скрипта, а не локальными для подпрограммы
CЭто то же самое, что с my, разницы нет
D@_ автоматически станет пустым