Скалярный и списковый контекст
Разбираемся с главной особенностью Perl, которая ставит в тупик почти всех новичков: одно и то же выражение может означать разные вещи в зависимости от того, «сколько значений» от него ожидают.
Контекст — то, каким образом Perl интерпретирует результат выражения: как одно значение (скалярный контекст) или как список значений (списковый контекст). Одно и то же выражение в разных контекстах может дать совершенно разные результаты.
Почему это вообще нужно понимать
Мы уже несколько раз в этом разделе сталкивались с намёками на контекст, не называя его прямо. Когда мы писали scalar(@massiv), чтобы получить количество элементов — это был как раз пример скалярного контекста. Пришло время разобрать эту идею полностью, потому что без неё поведение Perl-кода иногда кажется случайным и непредсказуемым, а на деле подчиняется чёткому, просто непривычному правилу.
Идея в двух словах такая: почти любое выражение в Perl — переменная, вызов функции, срез — «знает», ожидает ли от него код одно значение или список значений, и подстраивает свой ответ под этот запрос. Это фундаментально отличается от Python или JavaScript, где массив всегда остаётся массивом независимо от того, как его использовать. В Perl массив ведёт себя по-разному — и это не баг, а осознанное проектное решение Ларри Уолла, чтобы код можно было писать компактнее.
Массив в скалярном контексте — это число
Мы уже видели этот пример в уроке про массивы, но теперь разберём его как частный случай общего правила.
my @frukty = ("Yabloko", "Grusha", "Banan");
my $kolichestvo = @frukty;
print "Kolichestvo: $kolichestvo\n";Вывод:
Kolichestvo: 3Строка my $kolichestvo = @frukty; — самое интересное место в этом примере. Слева стоит скалярная переменная $kolichestvo, а справа — массив @frukty. Знак равенства заставляет Perl «спросить» у массива: «а что ты из себя представляешь в скалярном контексте?» — и массив отвечает не первым элементом, не последним, а количеством своих элементов. Это то самое поведение, которое явно вызывает функция scalar() — на самом деле scalar(@frukty) и my $x = @frukty дают один и тот же результат, просто scalar() делает намерение явным и читаемым для человека.
Тот же массив в списковом контексте — это все элементы
my @frukty = ("Yabloko", "Grusha", "Banan");
my @kopiya = @frukty;
print "Kopiya: @kopiya\n";
my ($pervyy, $vtoroy, $tretiy) = @frukty;
print "Po otdelnosti: $pervyy, $vtoroy, $tretiy\n";Вывод:
Kopiya: Yabloko Grusha Banan
Po otdelnosti: Yabloko, Grusha, BananЗдесь та же самая переменная @frukty ведёт себя совершенно иначе. В строке my @kopiya = @frukty; слева стоит массив, значит, Perl понимает, что от @frukty ожидают список — и просто копирует все элементы. А в строке my ($pervyy, $vtoroy, $tretiy) = @frukty; происходит ещё более интересная вещь: круглые скобки слева означают «список из трёх скалярных переменных», и Perl раскладывает элементы массива по этим трём переменным по порядку — первый элемент в $pervyy, второй в $vtoroy, третий в $tretiy. Это называется списковое присваивание, и это очень частый и удобный приём в реальном Perl-коде.
Сравнение двух контекстов на одной переменной
Чтобы разница стала совсем наглядной, положим оба варианта рядом.
my @chisla = (10, 20, 30, 40);
my $v_skalyarnom = @chisla;
my @v_spiskovom = @chisla;
print "Skalyarnyy kontekst: $v_skalyarnom\n";
print "Spiskovyy kontekst: @v_spiskovom\n";Вывод:
Skalyarnyy kontekst: 4
Spiskovyy kontekst: 10 20 30 40Один и тот же массив @chisla дал два совершенно разных результата — число 4 (сколько элементов) и полный список чисел — исключительно потому, что sigil переменной слева от знака равенства ($ или @) задал разный контекст. Это ключевая идея всего урока: контекст задаётся не самим выражением, а тем, как и куда его «кладут».
Списки в булевых условиях — тоже скалярный контекст
Проверка условия в if — ещё одно место, где Perl всегда требует скалярный контекст, даже если внутри стоит массив.
my @spisok = ();
if (@spisok) {
print "V spiske est elementy\n";
} else {
print "Spisok pust\n";
}
push @spisok, "element";
if (@spisok) {
print "Teper v spiske est elementy\n";
}Вывод:
Spisok pust
Teper v spiske est elementyЗдесь @spisok внутри if (...) оказывается в скалярном контексте — Perl превращает массив в число (количество элементов), и дальше это число проверяется как истина или ложь по обычному правилу Perl: 0 — ложь, любое другое число — истина. Пустой массив даёт 0 элементов, то есть ложь, а после добавления элемента функцией push массив содержит 1 элемент — истина. Программисту не нужно писать громоздкое if (scalar(@spisok) > 0) — достаточно просто if (@spisok), потому что контекст сам обо всём позаботится.
Мини-пример: подсчёт и вывод одновременно
Соберём идею контекста в практическом примере — сообщении о количестве покупок и их перечислении.
my @pokupki = ("Hleb", "Moloko", "Yaytsa");
my $skolko = @pokupki;
if ($skolko > 2) {
print "Bolshoy spisok iz $skolko pokupok: @pokupki\n";
} else {
print "Malenkiy spisok\n";
}Вывод:
Bolshoy spisok iz 3 pokupok: Hleb Moloko YaytsaПеременная $skolko получает массив @pokupki в скалярном контексте (число 3), а строка внутри print использует тот же массив @pokupki уже в контексте интерполяции строки, где он превращается в список элементов через пробел. Один и тот же массив, использованный дважды в двух разных ролях в пределах одного примера — и оба раза корректно.
Частые ошибки новичков
Самая частая ошибка — присвоить массив скалярной переменной, ожидая получить первый или последний элемент, а не количество. Новичок пишет my $x = @arr;, надеясь получить, например, первый элемент массива, а получает число элементов — и не понимает, откуда взялось «странное» значение, пока не узнает про контекст.
Вторая ошибка — противоположная: пытаться получить количество элементов, но случайно создать список из одной переменной. Например, запись my ($x) = @arr; (с круглыми скобками вокруг $x) — это списковое присваивание, и $x получит первый элемент массива, а не его длину, хотя выглядит почти как my $x = @arr; без скобок. Лишняя пара круглых скобок здесь всё меняет.
Третья ошибка — не замечать, что функция тоже может вести себя по-разному в разных контекстах (это уже более продвинутая тема, но полезно знать заранее): многие встроенные функции Perl проверяют контекст вызова через специальную конструкцию wantarray и возвращают разные данные в зависимости от того, ждут от них список или одно значение.
Итоги
- Контекст — это то, «сколько значений» ожидает код от выражения: скалярный контекст ждёт одно значение, списковый — список.
- Массив в скалярном контексте превращается в число — количество своих элементов; в списковом контексте остаётся полным списком элементов.
- Контекст определяется не самим выражением, а тем, куда его помещают: sigil переменной слева от
=, наличие круглых скобок, проверка вif— всё это задаёт контекст. - Условие
if (@arr)работает благодаря тому, что массив в скалярном (булевом) контексте превращается в число элементов, а 0 элементов означает ложь. - Эта особенность — не случайность и не усложнение ради усложнения, а фундаментальная черта Perl, позволяющая писать компактный код без лишних явных преобразований.