Как и разговорный язык, Perl в целом — это комбинация нескольких меньших, но взаимосвязанных частей. В отличие от разговорного языка, где оттенок и интонация голоса и интуиция позволяют людям общаться, несмотря на некоторое недопонимание и неясные представления, компьютеры и программный код требуют точности. Вы можете писать эффективный код на Perl, не зная каждой детали каждой возможности языка, но для написания хорошего кода вы должны понимать, как они работают вместе.
Имена (или идентификаторы) в программах на Perl повсюду: переменные, функции, пакеты, классы и даже дескрипторы файлов. Все эти имена начинаются с буквы или знака подчёркивания и могут включать любую комбинацию букв, цифр и знаков подчёркивания. Если задействована прагма utf8
(Юникод и строки), вы можете использовать в идентификаторах любые словарные символы UTF-8. Всё это допустимые в Perl идентификаторы:
my $name;
my @_private_names;
my %Names_to_Addresses;
sub anAwkwardName3;
# с включенным use utf8;
package Ingy::Döt::Net;
Такие идентификаторы недопустимы в Perl:
my $invalid name;
my @3;
my %~flags;
package a-lisp-style-name;
Имена существуют в первую очередь для пользы программиста. Эти правила применяются только к именам литералов, которые появляются в вашем исходном коде «как есть», таким как sub fetch_pie
или my $waffleiron
. Только парсер Perl принуждает следовать правилам об именах идентификаторов.
Динамическая натура Perl позволяет вам ссылаться на сущности с помощью имён, генерируемых во время исполнения или указанных как входные данные программы. Этот символьный поиск обеспечивает гибкость ценой некоторого ущерба безопасности. В частности, непрямой вызов функций или методов или поиск символов в пространстве имён позволяет вам обойти парсер Perl.
Такое поведение может привести к запутанному коду. Как эффективно рекомендует Марк Джейсон Доминус (Mark Jason Dominus) (footnote: http://perl.plover.com/varvarname.html), лучше используйте хеши или вложенные структуры данных (Вложенные структуры данных).
Имена переменных всегда имеют предшествующий сигил (или символ), который обозначает тип значения переменной. Скалярные переменные (Скаляры) используют знак доллара ($
). Переменные-массивы (Массивы) используют знак «at» («собака»), (@
). Переменные-хеши (Хеши) используют знак процента (%
):
my $scalar;
my @array;
my %hash;
Эти сигилы обеспечивают визуальное разделение пространств имён для имён переменных. Возможно — хотя это и может привести к путанице — объявить несколько переменных с одним и тем же именем, но разными типами:
my ($bad_name, @bad_name, %bad_name);
Хотя Perl и не будет сбит с толку, люди, читающие этот код, будут.
Сигилы Perl 5 — варьирующиеся сигилы. Как контекст определяет, сколько элементов вы ожидаете от операции, или какого типа данные вы ожидаете получить, так и сигил определяет, как вы манипулируете данными переменной. Например, для доступа к единственному элементу массива или хеша вы должны использовать сигил скаляра ($
):
my $hash_element = $hash{ $key };
my $array_element = $array[ $index ]
$hash{ $key } = 'value';
$array[ $index ] = 'item';
Параллель с контекстом количества важна. Использование скалярного элемента структуры как lvalue, левого значения (цель присвоения, находящаяся слева от знака =
), налагает скалярный контекст (Контекст) на rvalue, правое значение (присваемое значение, находящееся справа от знака =
).
Аналогично, доступ к нескольким элементам хеша или массива — операция, известная как получение среза — использует символ @
и налагает списочный контекст (footnote: …даже если сам список содержит ноль или один элемент):
my @hash_elements = @hash{ @keys };
my @array_elements = @array[ @indexes ];
my %hash;
@hash{ @keys } = @values;
Наиболее надёжный способ определить тип переменной — скаляр, массив или хеш — обратить внимание на производимые с ней операции. Скаляры поддерживают все базовые операции, такие как строковые, числовые и булевы манипуляции. Массивы поддерживают доступ по индексу посредством квадратных скобок. Хеши поддерживают доступ по ключу посредством фигурных скобок.
Perl предоставляет механизм группировки сходных функций и переменных в их собственные уникальные именованные пространства — пространства имён (Пакеты). Пространство имён — это именованная коллекция символов. Perl позволяет использование многоуровневых пространств имён, с именами, соединёнными двойным двоеточием (::
), где DessertShop::IceCream
ссылается на логическую коллекцию родственных переменных и функций, таких как scoop()
и pour_hot_fudge()
.
Внутри пространства имён вы можете использовать короткие имена его членов. Снаружи пространства имён ссылайтесь на его член, используя его полностью определённое имя. Таким образом, add_sprinkles()
внутри DessertShop::IceCream
ссылается на ту же самую функцию, что и DessertShop::IceCream::add_sprinkles()
снаружи пространства имён.
Хотя к именам пакетов применимы стандартные правила именования, по соглашению имена определённых пользователями пакетов начинаются с заглавных букв. Ядро Perl резервирует строчные имена пакетов для прагм ядра (Прагмы), таких как strict
и warnings
. Эта политика закреплена в первую очередь рекомендациями сообщества.
Все пространства имён в Perl 5 глобально видимы. Когда Perl ищет символ в DessertShop::IceCream::Freezer
, он ищет в символьной таблице main::
символ, представляющий пространство имён DessertShop::
, затем в нём — пространство имён IceCream::
, и так далее. Freezer::
видим снаружи пространства имён IceCream::
. Вложенность одного в другое — всего лишь механизм хранения, и ничего не говорит об отношениях между родительскими и дочерними пакетами, или пакетами, находящимися на одном уровне. Только программист может сделать логические отношения между сущностями очевидными — выбрав хорошие имена и правильно их организовав.
Переменная в Perl — это место хранения значения (Значения). Хотя простая программа может манипулировать значениями напрямую, большинство программ работают с переменными для упрощения логики кода. Переменные представляют значения; проще объяснить теорему Пифагора в терминах переменных a
, b
и c
, чем демонстрируя её принцип длинным списком корректных значений. Эта концепция может выглядеть очевидной, но эффективное программирование требует от вас справляться с искусством балансирования между общим и повторно используемым с одной стороны и конкретным — с другой.
Переменные видны частям вашей программы в зависимости от их области видимости (Область видимости). Большая часть переменных, с которыми вы встретитесь, имеет лексическую область видимости (Лексическая область видимости). Сами файлы задают свои собственные лексические области видимости, так что объявление package
само по себе новую область видимости не создаёт:
package Store::Toy;
my $discount = 0.10;
package Store::Music;
# переменная $discount всё ещё видима
say "Our current discount is $discount!";
Начиная с Perl 5.14, вы можете задать блок для объявления package
. Такой синтаксис создаёт лексическую область видимости:
package Store::Toy
{
my $discount = 0.10;
}
package Store::Music
{
# переменная $discount не доступна
}
package Store::BoardGame;
# переменная $discount всё ещё не доступна
Сигил переменной в объявлении определяет тип переменной: скаляр, массив или хеш. Сигил, используемый при доступе к переменной, варьируется в зависимости от того, что вы делаете с переменной. Например, вы объявляете массив как @values
. $values[0]
— доступ к первому элементу массива — единичному значению. @values[ @indices ]
— доступ к списку значений из массива.
Переменные в Perl не требуют имён. Имена существуют чтобы помочь вам, программисту, следить за $apple
, @barrels
или %cheap_meals
. Переменные, созданные в вашем программном коде без литеральных имён, называются анонимными переменными. Единственный способ обратиться к анонимной переменной — по ссылке (Ссылки).
Переменная в Perl 5 представляет как значение (курс доллара, доступные начинки для пиццы, гитарные магазины с телефонными номерами), так и контейнер, в котором хранится это значение. Система типов Perl имеет дело с типами значений и типами контейнеров. Тогда как тип контейнера переменной — скаляр, массив или хеш — не может измениться, относительно типа значения переменной Perl проявляет гибкость. Вы можете сохранить строку в переменной в одной строке кода, добавить к этой переменной число в следующей и переназначить ей ссылку на функцию (Ссылки на функции) в третьей.
Выполнение над переменной операции, требующей конкретного типа значения, может вызвать приведение (Приведение типов) из текущего типа значения переменной.
Например, документированный способ определить количество элементов в массиве — обратиться к нему в скалярном контексте (Контекст). Так как скалярная переменная может содержать только скаляр, присвоение массива скаляру налагает на операцию скалярный контекст, и массив, вычисленный в скалярном контексте, возвращает количество элементов в этом массиве:
my $count = @items;
Отношения между типами переменных, сигилами и контекстом имеют первостепенную важность.
Структура программы в большой степени зависит от способов, используя которые вы моделируете ваши данные с помощью соответствующих переменных.
Тогда как переменные позволяют производить абстрактные манипуляции с данными, значения, которые они содержат, делают программы конкретными и полезными. Чем более точны значения, тем лучше ваши программы. Эти значения — это данные: имя и адрес вашей тётушки, расстояние между вашим офисом и полем для игры в гольф на луне или вес всех печенек, которые вы съели за прошлый год. Внутри вашей программы правила, касающиеся формата этих данных, зачастую строги. Действенные программы требуют действенных (простых, быстрых, более компактных, более эффективных) способов представления их данных.
Строка — это кусок текстовых или двоичных данных без какого-либо особого форматирования или содержимого. Это может быть ваше имя, содержимое файла изображения или сама ваша программа. Строка имеет значение в программе только когда вы придаёте ей это значение.
Для представляения литеральной строки в вашей программе, окружите её парой символов заключения в кавычки. Наиболее распространённые ограничители строк — одиночные и двойные кавычки:
my $name = 'Donner Odinson, Bringer of Despair';
my $address = "Room 539, Bilskirnir, Valhalla";
Символы в строке, заключённой в одиночные кавычки, представляют в точности себя, за двумя исключениями. Одиночная кавычка, помещаемая в заключённую в одиночные кавычки строку, должна быть экранирована предшествующим обратным слешем:
my $reminder = 'Don\'t forget to escape '
. 'the single quote!';
Также вы должны экранировать обратный слеш, находящийся в конце строки, чтобы избежать экранирования закрывающего ограничителя и получения синтаксической ошибки:
my $exception = 'This string ends with a '
. 'backslash, not a quote: \\';
Любой другой обратный слеш станет частью строки как есть, если только два обратных слеша не расположены друг за другом, в этом случае первый будет экранировать второй:
is('Modern \ Perl', 'Modern \\ Perl',
'single quotes backslash escaping');
Для строки, заключённой в двойные кавычки, доступно ещё некоторое количество специальных символов. Например, вы можете закодировать в строке иначе невидимые пробельные символы:
my $tab = "\t";
my $newline = "\n";
my $carriage = "\r";
my $formfeed = "\f";
my $backspace = "\b";
Это демонстрирует полезный принцип: синтаксис, используемый для объявления строки, может варьироваться. Вы можете представить символ табуляции в строке как \t
или введя его напрямую. С точки зрения Perl, обе строки ведут себя одинаково, несмотря на то, что конкретное представление строки в исходном коде может отличаться.
Объявление строки может пересекать логические переводы строк; эти два объявления эквивалентны:
my $escaped = "two\nlines";
my $literal = "two
lines";
is $escaped, $literal, 'equivalent \n and newline';
Эти последовательности зачастую проще читать, чем их пробельные эквивалентны.
Строки в Perl имеют переменную длину. По мере того, как вы манипулируете строками и модифицируете их, Perl будет соответствующим образом изменять их размер. Например, вы можете объединить несколько строк в одну большую строку с помощью оператора конкатенации .
:
my $kitten = 'Choco' . ' ' . 'Spidermonkey';
Это фактически то же самое, что и инициализация строки сразу целиком.
Также вы можете интерполировать значение скалярной переменной или значений массива в строке, заключённой в двойные кавычки, так что текущее содержимое переменной станет частью строки, как если бы вы их конкатенировали:
my $factoid = "$name lives at $address!";
# эквивалентно
my $factoid = $name . ' lives at ' . $address . '!';
Включение двойной кавычки в заключённую в двойные кавычки строку требует её экранирования (то есть предварения её обратным слешем):
my $quote = "\"Ouch,\", he cried. \"That hurt!\"";
Когда повторение обратных слешей становится громоздким, используйте альтернативный оператор заключения в кавычки, для которого вы можете выбрать альтернативный ограничитель строк. Оператор q
аналогичен одиночным кавычкам, тогда как оператор qq
обеспечивает поведение двойных кавычек. Символ, следующий сразу за оператором, определяет, какой символ используется для ограничения строк. Если этот символ является открывающим символом симметричной пары — такой как открывающая и закрывающая скобки — закрывающий символ будет конечным ограничителем. В ином случае, сам указанный символ будет как начальным, так и конечным ограничителем.
my $quote = qq{"Ouch", he said. "That hurt!"};
my $reminder = q^Don't escape the single quote!^;
my $complaint = q{It's too early to be awake.};
Если объявление сложной строки, содержащей последовательность экранированных символов, слишком утомительно, используйте синтаксис heredoc (встроенных документов) для присвоения строке одной или нескольких строчек текста:
my $blurb =<<'END_BLURB';
He looked up. "Time is never on our side, my child.
Do you see the irony? All they know is change.
Change is the constant on which they all can agree.
We instead, born out of time, remain perfect and
perfectly self-aware. We only suffer change as we
pursue it. It is against our nature. We rebel
against that change. Shall we consider them
greater for it?"
END_BLURB
Синтаксис <<'END_BLURB'
имеет три части. Двойные угловые скобки открывают встроенный документ. Кавычки определяют, будет ли он проявлять поведение одиночных или двойных кавычек. Поведение по умолчанию — интерполяция двойных кавычек. END_BLURB
— произвольный идентификатор, который парсер Perl 5 использует как конечный ограничитель.
Будьте внимательны — независимо от отступа самого объявления встроенного документа, конечный ограничитель должен быть расположен в начале строки:
sub some_function {
my $ingredients =<<'END_INGREDIENTS';
Two eggs
One cup flour
Two ounces butter
One-quarter teaspoon salt
One cup milk
One drop vanilla
Season to taste
END_INGREDIENTS
}
Если идентификатор начинается с пробельных символов, те же самые пробельные символы должны присутствовать перед конечным ограничителем. Тем не менее, если вы сделаете отступ у идентификатора, Perl 5 не будет удалять соответствующие пробелы из начала каждой строки встроенного документа.
Использование строки в нестроковом контексте приведёт к приведению типа (Приведение типов).
Юникод — это система представления символов письменных языков мира. В то время как большая часть английского текста использует набор символов, состоящий всего из 127 символов (что требует для хранения семи битов и замечательно вписывается в восьмибитовый байт), наивно предполагать, что вам рано или поздно не понадобится умляут.
Строки в Perl 5 могут представлять один из двух отдельных, но связанных типов данных:
Каждый символ имеет код, уникальное число, которое идентифицирует его в таблице символов Юникод.
Двоичные данные — это последовательность октетов, восьмибитовых чисел, каждое из которых может представлять число от 0 до 255.
Почему octet а не байт? Предположение, что один символ помещается в один байт, принесёт вам бесконечную юникодовую скорбь. Разделяйте идеи хранения в памяти и представления символов.
Юникодовые строки и двоичные строки выглядят похоже. Каждая имеет длину (length()
). Каждая поддерживает стандартные строковые операции, такие как конкатенация, работа с подстроками и обработка регулярными выражениями. Любая строка, не являющаяся чистыми двоичными данными, есть текстовые данные, и должна быть представлена последовательностью символов Юникод.
Однако, из-за того, как ваша операционная система представляет данные на диске, или полученные от пользователей, или передаваемые по сети — как последовательность октетов — Perl не может знать, являются ли читаемые вами данные файлом изображения, или текстовым документом, или чем-нибудь ещё. По умолчанию Perl воспринимает все входящие данные как последовательность октетов. Вы должны наделить эти данные конкретным значением.
Строка Юникод — это последовательность октетов, которая представляет последовательность символов. Кодировка Юникод устанавливает соответствие последовательностей октетов символам. Некоторые кодировки, такие как UTF-8, могут кодировать все символы из набора символов Юникод. Другие кодировки представляют его подмножество. Например, ASCII кодирует простой английский текст без диакритических символов, тогда как Latin-1 может представлять текст на большинстве языков, использующих латинский алфавит.
Чтобы избежать большинства проблем с Юникодом, всегда используйте соответствующие кодировки для декодирования входных и выходных данных вашей программы.
Perl 5.12 поддерживает стандарт Юникод 5.2, тогда как Perl 5.14 поддерживает Юникод 6.0. Если вам нужно заботиться о различиях между версиями Юникод, вы, вероятно, уже знаете, что нужно смотреть на http://unicode.org/versions/.
Если вы скажете Perl, что определённый дескриптор файла (Файлы) работает с кодированным текстом, Perl будет конвертировать входящие октеты в строки Юникод автоматически. Чтобы это сделать, добавьте слой ввода-вывода к режиму встроенной функции open
. Слой ввода-вывода оборачивается вокруг входа и выхода и конвертирует данные. В данном случае, слой :utf8
декодирует данные в UTF-8:
use autodie;
open my $fh, '<:utf8', $textfile;
my $unicode_string = <$fh>;
Также вы можете модифицировать существующий дескриптор файла с помощью binmode
, как для ввода, так и для вывода:
binmode $fh, ':utf8';
my $unicode_string = <$fh>;
binmode STDOUT, ':utf8';
say $unicode_string;
Без режима utf8
печать строк Юникод в дескриптор файла приведёт к предупреждению (Wide character in %s
), потому что файлы содержат октеты, а не символы Юникод.
Базовый модуль Encode
предоставляет функцию, называемую decode()
, для конвертации скаляра, содержащего данные, в строку Юникод. Соответствующая функция encode()
конвертирует из внутренней кодировки Perl в желаемую выходную кодировку:
my $from_utf8 = decode('utf8', $data);
my $to_latin1 = encode('iso-8859-1', $string);
Вы можете включать символы Юникод в ваши программы тремя способами. Самый простой — использовать прагму utf8
(Прагмы), которая указывает парсеру Perl интерпретировать остальную часть файла исходного кода как имеющую кодировку UTF-8. Это позволяет вам использовать символы Юникод в строках и идентификаторах:
use utf8;
sub £_to_¥ { ... }
my $yen = £_to_¥('1000£');
Чтобы писать такой код, ваш текстовый редактор должен понимать UTF-8, а вы должны сохранить файл в соответствующей кодировке.
Внутри строк в двойных кавычках вы можете использовать экранированные последовательности Юникод для представления кодировок символов. Синтаксис \x{}
представляет один символ; поместите в фигурных скобках шестнадцатиричную форму номера символа в Юникод:
my $escaped_thorn = "\x{00FE}";
Некоторые символы Юникод имеют имена, и эти имена зачастую проще читать, чем номера Юникод. Используйте прагму charnames
для того, чтобы их включить, и \N{}
, чтобы на них ссылаться:
use charnames ':full';
use Test::More tests => 1;
my $escaped_thorn = "\x{00FE}";
my $named_thorn = "\N{LATIN SMALL LETTER THORN}";
is $escaped_thorn, $named_thorn,
'Thorn equivalence check';
Вы можете использовать формы \x{}
и \N{}
в регулярных выражениях, так же как и в любых местах, где допустимо использовать строку или символ.
Большинство проблем Юникод в Perl проистекают из того факта, что строка может быть или последовательностью октетов, или последовательностью символов. Perl позволяет вам комбинировать эти типы посредством неявных конвертаций. Когда эти конвертации неверны, они редко очевидно неверны.
Когда Perl конкатенирует последовательность октетов с последовательностью символов Юникод, он неявно декодирует последовательность октетов, используя кодировку Latin-1. Результирующая строка будет содержать символы Юникод. Когда вы печатаете символы Юникод, Perl кодирует строку, используя UTF-8, потому что Latin-1 не может отображать весть набор символов Юникод: Latin-1 — подмножество UTF-8.
Эта асимметрия может привести к строкам Юникод, закодированным как UTF-8 для вывода и декодированным как Latin-1 на вводе.
Хуже того, если текст содержит только английские символы без знаков диакритики, то ошибка скрывается — потому что обе кодировки одинаково отображают каждый символ.
my $hello = "Hello, ";
my $greeting = $hello . $name;
Если $name
содержит английское имя, такое как Alice, вы никогда не заметите никаких проблем, потому что представление Latin-1 точно такое же, как представление UTF-8. Если $name
содержит такое имя, как José, $name
может содержать несколько возможных значений:
$name
содержит четыре символа Юникод.$name
содержит четыре октета Latin-1, представляющих четыре символа Юникод.$name
содержит пять октетов UTF-8, представляющих четыре символа Юникод.Строковый литерал имеет несколько возможных сценариев:
my $hello = "Hello, ";
my $hello = "¡Hola, ";
Строковый литерал содержит октеты.
utf8
или encoding
, содержащий символы Юникод. use utf8;
my $hello = "Kuirabá, ";
Если и $hello
, и $name
— строки Юникод, конкатенация порождает другую строку Юникод.
Если обе строки — потоки октетов, Perl конкатенирует их в новую строку, содержащую октеты. Если оба значения — октеты с одной и той же кодировкой, например, оба — Latin-1, конкатенация сработает корректно. Если же октеты имеют разную кодировку, например, конкатенация добавляет данные в UTF-8 к данным в Latin-1, то результирующая последовательность октетов не будет иметь смысла ни в какой из кодировок. Это может случиться, если пользователь ввёл имя как данные UTF-8, а приветствие было строковым литералом Latin-1, но программа не декодировала ни то, ни другое.
Если только одно из значений — строка Юникод, Perl будет декодировать другое как данные Latin-1. Если это не корректная кодировка, результирующие символы Юникод будут неверными. Например, если пользовательский ввод был данными в UTF-8, а строковый литерал был строкой Юникод, имя было бы некорректно декодировано в пять символов Юникод в виде José (именно!) вместо José, потому что данные UTF-8 означают что-то совсем другое, когда декодируются как данные Latin-1.
Смотрите perldoc perluniintro
для получения намного более детального объяснения Юникод, кодировок, и того, как управлять входящими и исходящими данными в мире Юникод (footnote: Для ещё больших подробностей на тему эффективного управления Юникодом в ваших программах, смотрите ответ Тома Кристиансена (Tom Christiansen) на вопрос «Почему Современный Perl избегает UTF-8 по умолчанию?» http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/6163129#6163129).
В Perl 5.12 была добавлена возможность,
unicode_strings
, которая активирует семантику Юникод для всех строковых операций в своей области видимости. Perl 5.14 улучшил эту возможность; если вы работаете с Юникодом в Perl, стоит обновиться хотя бы до Perl 5.14.
Perl поддерживает числа в виде как целых значений, так и значений с плавающей точкой. Вы можете выражать их в научной нотации, так же как в двоичной, восьмеричной и шестнадцатиричной формах:
my $integer = 42;
my $float = 0.007;
my $sci_float = 1.02e14;
my $binary = 0b101010;
my $octal = 052;
my $hex = 0x20;
Выделенные жирным символы — числовые префиксы для двоичной, восьмеричной и шестнадцатиричной нотации соответственно. Имейте ввиду, что ведущий ноль в целом числе всегда означает восьмеричный режим.
Хотя в Perl 5 вы и можете явно записывать значения с плавающей точкой с совершенной точностью, Perl 5 внутренне хранит их в двоичном формате. Это представление иногда неточно специфическим образом; проконсультируйтесь с perldoc perlnumber
для больших деталей.
Вы не можете использовать запятые для отделения тысяч в числовых литералах, потому что парсер сочтёт запятую оператором запятой. Вместо этого используйте в числах подчёркивания. Парсер будет будет воспринимать их как невидимые символы; ваши читатели, возможно, нет. Эти записи равнозначны:
my $billion = 1000000000;
my $billion = 1_000_000_000;
my $billion = 10_0_00_00_0_0_0;
Подумайте, какой вариант наиболее читабелен.
Из-за приведения типа (Приведение типов), Perl-программистам редко приходится беспокоиться о конвертации текста, прочитанного из-за пределов программы, в числа. Perl будет обращаться со всем, что выглядит как число, как с числом в числовом контексте. В редких ситуациях, когда вам нужно знать, выглядит ли нечто как число для Perl, используйте функцию looks_like_number
из базового модуля Scalar::Util
. Эта функция возвращает истину, если Perl будет считать переданный аргумент числовым.
Модуль Regexp::Common
из CPAN предоставляет несколько хорошо протестированных регулярных выражений для определения более конкретных допустимых типов числовых значений (целое число, целое значение, значение с плавающей точкой).
Значение undef
в Perl 5 представляет собой неприсвоенное, неопределённое и неизвестное значение. Объявленные, но не определённые скалярные переменные всегда содержат undef
:
my $name = undef; # необязательное присваивание
my $rank; # тоже содержит undef
undef
даёт ложь в булевом контексте. Использование undef
в строковом контексте — например, интерполяция его в строке, — генерирует предупреждение uninitialized value
:
my $undefined;
my $defined = $undefined . '... and so forth';
…выдаёт:
Use of uninitialized value $undefined in
concatenation (.) or string...
Встроенная функция defined
возвращает истинное значение, если её операнд является определённым значением (любым отличным от undef
):
my $status = 'suffering from a cold';
say defined $status; # 1, истинное значение
say defined undef; # пустая строка, ложное значение
При использовании справа от присваивания, конструкция ()
обозначает пустой список. В скалярном контексте он вычисляется в undef
. В списочном контексте это пустой список. При использовании слева от присваивания, конструкция ()
налагает списочный контекст. Для подсчёта количества элементов, возвращаемых выражением в списочном контексте, без использования временной переменной, используйте следующую идиому (Идиомы):
my $count = () = get_all_clown_hats();
Из-за правой ассоциативности (Ассоциативность) оператора присваивания, Perl сначала вычисляет второе присваивание, вызывая get_all_clown_hats()
в списочном контексте. Это генерирует список.
Присваивание пустому списоку отбрасывает все значения из списка, но это присваивание происходит в скалярном контексте, что выдаёт количество элементов на правой стороне присваивания. В результате $count
содержит количество элементов в списке, возвращаемом get_all_clown_hats()
.
Если сейчас эта концепция кажется вам сбивающей с толку, не бойтесь. Когда вы поймёте, как фундаментальные особенности дизайна Perl взаимодействуют на практике, это будет иметь больше смысла.
Список — это разделённая запятыми группа из одного или более выражений. Списки могут появляться в исходном коде буквально как значения:
my @first_fibs = (1, 1, 2, 3, 5, 8, 13, 21);
…как цели присваивания:
my ($package, $filename, $line) = caller();
…или списки выражений:
say name(), ' => ', age();
Скобки не создают списки. Списки создаёт оператор запятая. Скобки, там, где они присутствуют в этих примерах, группируют выражения для изменения их приоритета (Приоритет).
Используйте оператор диапазона для создания списков литералов в компактной форме:
my @chars = 'a' .. 'z';
my @count = 13 .. 27;
Используйте оператор qw()
чтобы разбить литеральную строку по пробелам и получить список строк:
my @stooges = qw( Larry Curly Moe Shemp Joey Kenny );
Perl покажет предупреждение, если qw()
содержит запятую или символ комментария (#
), потому что такие символы не только редки в qw()
, но их присутствие обычно указывает на недосмотр.
Списки могут (и это часто происходит) быть результатами выражений, но эти списки не появляются буквально в исходном коде.
Списки и массивы в Perl не взаимозаменяемы. Списки — это значения. Массивы — это контейнеры. Вы можете сохранить список в массиве или преобразовать массив в список, но это разные сущности. Например, индексация в списке всегда происходит в списочном контексте. Индексация в массиве может происходить в скалярном контексте (для единственного элемента) или списочном контексте (для среза):
# пока не беспокойтесь о деталях
sub context
{
my $context = wantarray();
say defined $context
? $context
? 'list'
: 'scalar'
: 'void';
return 0;
}
my @list_slice = (1, 2, 3)[context()];
my @array_slice = @list_slice[context()];
my $array_index = $array_slice[context()];
say context(); # списочный контекст
context(); # пустой контекст
Базовый поток управления в Perl — прямой. Выполнение программы начинается с начала (первая строка исполняемого файла) и продолжается до конца:
say 'At start';
say 'In middle';
say 'At end';
Директивы потока управления в Perl изменяют порядок выполнения — что в программе произойдёт следущим — в зависимости от значений их выражений.
Директива if
выполняет связанное действие только если её условное выражение возвращает истинное значение:
say 'Hello, Bob!' if $name eq 'Bob';
Эта постфиксная форма удобна для простых выражений. Блочная форма группирует несколько выражений воедино:
if ($name eq 'Bob')
{
say 'Hello, Bob!';
found_bob();
}
Блочная форма требует скобок вокруг своего условия, постфиксная же форма — нет:
Условное выражение может состоять из нескольких подвыражений, до тех пор пока оно вычисляется в одно выражение верхнего уровня:
if ($name eq 'Bob' && not greeted_bob())
{
say 'Hello, Bob!';
found_bob();
}
В постфиксной форме добавление скобок может прояснить замысел кода ценой визуальной чистоты:
greet_bob() if ($name eq 'Bob' && not greeted_bob());
Директива unless
— отрицательная форма if
. Perl выполнит действие в случае, если условное выражение возвращает ложь:
say "You're not Bob!" unless $name eq 'Bob';
Как и if
, unless
тоже имеет блочную форму, хотя многие программисты избегают её, потому что со сложными условиями она очень быстро становится сложной для чтения:
unless (is_leap_year() and is_full_moon())
{
frolic();
gambol();
}
unless
очень хорошо работает для постфиксных условий, особенно для валидации параметров в функциях (Постфиксная валидация параметров):
sub frolic
{
return unless @_;
for my $chant (@_) { ... }
}
Блочные формы как if
, так и unless
, работают с директивой else
, предоставляющей код для выполнения в случае, если условное выражение не возвращает истину (для if
) или ложь (для unless
):
if ($name eq 'Bob')
{
say 'Hi, Bob!';
greet_user();
}
else
{
say "I don't know you.";
shun_user();
}
Блоки else
позволяют переписать условия if
и unless
в терминах друг друга:
unless ($name eq 'Bob')
{
say "I don't know you.";
shun_user();
}
else
{
say 'Hi, Bob!';
greet_user();
}
Однако, подразумеваемое двойное отрицание при использовании unless
с блоком else
может сбивать с толку. Этот пример, возможно, единственное место, где вы его когда-либо увидите.
Perl не только предоставляет вам и if
, и unless
, чтобы дать возможность выразить ваши условия наиболее читаемым способом, вы также можете выбирать между положительными и отрицательными условными операторами:
if ($name ne 'Bob')
{
say "I don't know you.";
shun_user();
}
else
{
say 'Hi, Bob!';
greet_user();
}
…хотя двойное отрицание, создаваемое присутствием блока else
, наводит на мысль инвертировать условие.
Одна или более директив elsif
может следовать за блочной формой if
и предшествовать единственной директиве else
:
if ($name eq 'Bob')
{
say 'Hi, Bob!';
greet_user();
}
elsif ($name eq 'Jim')
{
say 'Hi, Jim!';
greet_user();
}
else
{
say "You're not my uncle.";
shun_user();
}
Цепочка unless
тоже может использовать блок elsif
(footnote: Удачи с расшифровкой этого!). Однако, не существует директивы elseunless
.
Написание else if
— синтаксическая ошибка (footnote: Ларри предпочитает elsif
исходя из эстетических причин, а также предшествующего искусства языка програмирования Ada.):
if ($name eq 'Rick')
{
say 'Hi, cousin!';
}
# внимание, синтаксическая ошибка
else if ($name eq 'Kristen')
{
say 'Hi, cousin-in-law!';
}
Тернарный условный оператор вычисляет условное выражение и возвращает одну из двух альтернатив:
my $time_suffix = after_noon($time)
? 'afternoon'
: 'morning';
Условное выражение предшествует символу вопросительного знака (?
), а символ двоеточия (:
) разделяет альтернативы. Альтернативы — это выражения произвольной сложности, включая другие тернарные условные выражения.
Интересная, хотя и малоизвестная, идиома — использование тернарного условия для выбора между альтернативными переменными, а не только значениями:
push @{ rand() > 0.5 ? \@red_team : \@blue_team }, Player->new;
И снова, взвесьте выгоды ясности против выгод краткости.
Perl проявляет поведение, называемое коротким замыканием, когда встречается со сложными условными выражениями. Если Perl может определить, будет ли сложное выражение истинно или ложно, не вычисляя каждое подвыражение, он не будет вычислять последующие подвыражения. Это наиболее очевидно на примере:
say "Both true!" if ok( 1, 'subexpression one' )
&& ok( 1, 'subexpression two' );
done_testing();
Возвращаемое значение ok()
(Тестирование) — это булево значение, полученное при вычислении первого аргумента, поэтому этот код выведет:
ok 1 - subexpression one
ok 2 - subexpression two
Both true!
Если первое подвыражение — первый вызов ok
— возвращает истинное значение, Perl должен будет вычислить второе подвыражение. Если бы первое подвыражение вернуло ложное значение, то не было бы необходимости проверять последующие подвыражения, так как всё выражение уже не может быть истинным:
say "Both true!" if ok( 0, 'subexpression one' )
&& ok( 1, 'subexpression two' );
Этот пример выводит:
not ok 1 - subexpression one
Хотя второе подвыражение очевидно вернёт истину, Perl никогда не вычислит его. То же самое короткое замыкание очевидно для операций логического или:
say "Either true!" if ok( 1, 'subexpression one' )
|| ok( 1, 'subexpression two' );
Этот пример выводит:
ok 1 - subexpression one
Either true!
В случае истинности первого подвыражения, Perl может избежать вычисления второго подвыражения. Если бы первое подвыражение было ложным, результат вычисления второго подвыражения определил бы результат вычисления всего выражения.
Короткое замыкание позволяет вам не только избежать потенциально дорогих вычислений, но и избежать ошибок и предупреждений, как в случае, когда использование неопределённого значения может вызвать предупреждение:
my $bbq;
if (defined $bbq and $bbq eq 'brisket') { ... }
Все условные директивы — if
, unless
и тернарный условный оператор — вычисляют выражение в булевом контексте (Контекст). В то время как операторы сравнения, такие как eq
, ==
, ne
и !=
, возвращают булевы значения при их вычислении, результаты других выражений — включая переменные и значения — Perl преобразует в булевы формы.
В Perl 5 нет единого истинного значения, как и единого ложного. Любое число, преобразуемое в 0, ложно. Это относится к 0
, 0.0
, 0e0
, 0x0
и т. д. Пустая строка (''
) и '0'
преобразуются в ложное значение, но строки '0.0'
, '0e0'
и т. д. — нет. Идиома '0 but true'
преобразуется в 0 в числовом контексте, но истинно в булевом контексте, благодаря своему строковому содержанию.
Пустой список и undef
преобразуются в ложное значение. Пустые массивы и хеши возвращают число 0 в скалярном контексте, поэтому в булевом контексте преобразуются в ложь. Массив, содержащий хотя бы один элемент, пусть даже undef
, преобразуется в истину в булевом контексте. Хеш, содержащий хотя бы один элемент, даже если и ключ, и значение — undef
, преобразуется в истинное значение в булевом контексте.
Модуль Want
из CPAN позволяет вам определять булев контекст с помощью своих собственных функций. Встроенная прагма overloading
(Перегрузка) позволяет вам указать, что ваши собственные типы данных возвращают при вычислении в разных контекстах.
Perl предоставляет несколько директив для организации циклов и итераций. Цикл в стиле foreach вычисляет выражение, возвращающее список, и выполняет инструкцию или блок, пока не израсходует весь этот список:
foreach (1 .. 10)
{
say "$_ * $_ = ", $_ * $_;
}
Этот пример использует оператор диапазона для генерации списка целых чисел от единицы до десяти включительно. Директива foreach
циклически проходит по ним, устанавливая переменную-топик $_
(Подразумеваемая переменная-скаляр) в каждое из них по очереди. Perl выполняет блок для каждого целого числа и выводит их квадраты.
Многие Perl-программисты подразумевают под итерациями циклы foreach
, но Perl считает имена foreach
и for
взаимозаменяемыми. Последующий код определяет тип и поведение цикла.
Как и if
и unless
, этот цикл имеет постфиксную форму:
say "$_ * $_ = ", $_ * $_ for 1 .. 10;
Цикл for
может использовать именованную переменную вместо переменной-топика:
for my $i (1 .. 10)
{
say "$i * $i = ", $i * $i;
}
Когда цикл for
использует переменную-итератор, область видимости этой переменной — внутри цикла. Perl будет назначать этой лексической переменной значение каждого элемента итерации. Perl не будет модифицировать переменную-топик ($_
). Если вы объявили лексическую переменную $i
во внешней области видимости, её значение сохранится снаружи цикла:
my $i = 'cow';
for my $i (1 .. 10)
{
say "$i * $i = ", $i * $i;
}
is( $i, 'cow', 'Value preserved in outer scope' );
Эта локализация происходит даже если вы не переобъявляли переменную итерации как лексическую (footnote: —однако объявляйте ваши переменные итераций как лексические, чтобы уменьшить их область видимости.):
my $i = 'horse';
for $i (1 .. 10)
{
say "$i * $i = ", $i * $i;
}
is( $i, 'horse', 'Value preserved in outer scope' );
Цикл for
создаёт переменную-итератор как псевдоним для значения в итерации, так что любые модификации значения итератора сразу же изменяют исходное значение:
my @nums = 1 .. 10;
$_ **= 2 for @nums;
is( $nums[0], 1, '1 * 1 is 1' );
is( $nums[1], 4, '2 * 2 is 4' );
...
is( $nums[9], 100, '10 * 10 is 100' );
Создание псевдонимов также работает с блочным стилем цикла for
:
for my $num (@nums)
{
$num **= 2;
}
…как и в итерациях с переменной-топиком:
for (@nums)
{
$_ **= 2;
}
Однако, вы не можете использовать псевдонимы для изменения значения констант:
for (qw( Huex Dewex Louid ))
{
$_++;
say;
}
Вместо этого Perl выбросит исключение о модификации значения, предназначенного только для чтения.
Иногда вы можете увидеть использование цикла for
с единственной скалярной переменной, для создания псевдонима этой переменной в $_
:
for ($user_input)
{
s/\A\s+//; # удалить ведущие пробелы
s/\s+\z//; # удалить завершающие пробелы
$_ = quotemeta; # экранировать символы, не являющиеся словарными символами
}
Учёт области видимости итератора в случае переменной-топика создаёт общий источник путаницы. Рассмотрим функцию topic_mangler()
, целенаправленно модифицирующую $_
. Если код, итерирующий по списку, вызывает topic_mangler()
, не защитив $_
, весёлая отладка гарантированна:
for (@values)
{
topic_mangler();
}
sub topic_mangler
{
s/foo/bar/;
}
Если вам приходится использовать $_
вместо именованной переменной, сделайте переменную-топик лексической с помощью объявления my $_
:
sub topic_mangler
{
# было $_ = shift;
my $_ = shift;
s/foo/bar/;
s/baz/quux/;
return $_;
}
Использование именованной переменной итерации также предотвращает нежелательное создание псевдонима в $_
.
Цикл for
в стиле C требует управления условиями итерации:
for (my $i = 0; $i <= 10; $i += 2)
{
say "$i * $i = ", $i * $i;
}
Вы должны явно назначить значение итерационной переменной в конструкции цикла, так как этот цикл не выполняет ни создания псевдонима, ни присвоения переменной-топику. Хотя все переменные, объявленные в конструкции цикла, имеют лексическую область видимости в блоке цикла, переменные, объявленные снаружи конструкции цикла, не локализуются:
my $i = 'pig';
for ($i = 0; $i <= 10; $i += 2)
{
say "$i * $i = ", $i * $i;
}
isnt( $i, 'pig', '$i overwritten with a number' );
Конструкция цикла может иметь три подвыражения. Первое подвыражение — секция инициализации — выполняется только один раз, перед выполнением тела цикла. Perl вычисляет второе подвыражение — условное сравнение — перед каждой итерацией тела цикла. Если оно возвращает истинное значение, выполняется итерация. Если оно возвращает ложное значение, итерация прекращается. Последнее подвыражение выполняется после каждой итерации тела цикла.
for (
# подвыражение инициализации цикла
say 'Initializing', my $i = 0;
# подвыражение условного сравнения
say "Iteration: $i" and $i < 10;
# подвыражение завершения итерации
say 'Incrementing ' . $i++
)
{
say "$i * $i = ", $i * $i;
}
Обратите внимание на отсутствие точки с запятой после последнего подвыражения, а также на использование оператора запятой и низкоприоритетного and
; этот синтаксис на удивление привередлив. По возможности, предпочтите использование цикла в стиле foreach
, а не for
.
Все три подвыражения необязательны. Бесконечный цикл for
может выглядеть так:
for (;;) { ... }
Цикл while продолжается до тех пор, пока условие цикла не вернёт ложное булево значение. Идиоматический бесконечный цикл выглядит так:
while (1) { ... }
В отличие от итерации в цикле стиля foreach
, условие цикла while
само по себе не имеет побочных эффектов. То есть, если @values
содержит один или несколько элементов, этот код тоже будет бесконечным циклом:
while (@values)
{
say $values[0];
}
Чтобы предотвратить подобный бесконечный цикл while
, используйте деструктивное изменение массива @values
, модифицируя массив на каждой итерации цикла:
while (@values)
{
my $value = shift @values;
say $value;
}
Модификация @values
внутри условия while
тоже работает, но имеет ряд тонкостей, связанных с истинностью каждого значения.
while (my $value = shift @values)
{
say $value;
}
Этот цикл прекратится, как только он достигнет элемента, являющегося ложным значением, а не обязательно, когда весь массив будет израсходован. Это может быть ожидаемым поведением, но часто удивляет новичков.
Цикл until меняет на противоположный смысл проверки цикла while
. Итерация продолжается, пока условное выражение цикла возвращает ложное значение:
until ($finished_running)
{
...
}
Каноническое использование цикла while
— итерация по входным данным из дескриптора файла:
use autodie;
open my $fh, '<', $file;
while (<$fh>)
{
...
}
Perl 5 интерпретирует этот цикл while
как если бы вы написали:
while (defined($_ = <$fh>))
{
...
}
Без явного defined
, любая строка, прочитанная из дескриптора файла, которая в скалярном контексте даёт ложное значение — пустая строка, или строка, содержащая один только символ 0
— завершит цикл. Оператор readline
(<>
) возвращает неопределённое значение только когда достигает конца файла.
Используйте встроенную функцию chomp
для удаления символов конца строки из каждой строчки. Многие новички забывают это делать.
И while
, и until
имеют постфиксные формы, такие как бесконечный цикл 1 while 1;
. Любое одиночное выражение годится для постфиксной формы while
или until
, включая классический пример «Hello, world!» из 8-битных компьютеров ранних 1980-ых:
print "Hello, world! " while 1;
Бесконечные циклы более полезны, чем кажется на первый взгляд, особенно для петель событий в программах с графическим интерфейсом, интерпретаторах или сетевых серверах:
$server->dispatch_results() until $should_shutdown;
Используйте блок do
, чтобы сгруппировать несколько выражений воедино:
do
{
say 'What is your name?';
my $name = <>;
chomp $name;
say "Hello, $name!" if $name;
} until (eof);
Блок do
воспринимается парсером как единое выражение, которое может содержать несколько других выражений. В отличие от блочной формы цикла while
, блок do
с постфиксным while
или until
выполнит своё тело хотя бы раз. Эта конструкция менее распространена, чем другие формы циклов, но не менее действенна.
Вы можете вкладывать циклы друг в друга:
for my $suit (@suits)
{
for my $values (@card_values) { ... }
}
Когда вы это делаете, объявляйте именованные итерационные переменные! В противном случае вероятность возникновения путаницы с переменной-топиком и её областью видимости слишком высока.
Распространённая ошибка при вложении циклов foreach
и while
заключается в том, что легко израсходовать дескриптор файла в цикле while
:
use autodie;
open my $fh, '<', $some_file;
for my $prefix (@prefixes)
{
# НЕ ИСПОЛЬЗУЙТЕ; с большой вероятностью ошибочный код
while (<$fh>)
{
say $prefix, $_;
}
}
Открытие дескриптора файла снаружи цикла for
сохраняет позицию в файле между итерациями цикла for
. На его второй итерации циклу while
будет нечего читать, и он не будет выполнен. Чтобы разрешить эту проблему, переоткрывайте файл внутри цикла for
(просто для понимания, но не всегда хорошее использование системных ресурсов), считайте весь файл в память (что может не сработать, если файл большой) или сбрасывайте дескриптор файла назад к началу файла на каждой итерации с помощью функции seek
(опция, которую зачастую упускают):
use autodie;
open my $fh, '<', $some_file;
for my $prefix (@prefixes)
{
while (<$fh>)
{
say $prefix, $_;
}
seek $fh, 0, 0;
}
Иногда вам нужно разорвать цикл раньше, чем условия итерации будут исчерпаны. Стандартные механизмы управления Perl 5 — исключения и return
— работают, но кроме того вы можете использовать операторы контроля цикла.
Оператор next перезапускает цикл со следующей его итерации. Используйте его, если вы сделали всё, что нужно, в текущей итерации. Для цикла по строкам в файле с пропуском любой строки, начинающейся с символа комментария #
, напишите:
while (<$fh>)
{
next if /\A#/;
...
}
Сравните использование next
с альтернативой: заворачивание оставшейся части тела блока в if
. Представьте, что произойдёт, если у вас есть несколько условий, которые могут вызвать пропуск строки. Модификатры контроля цикла с постфиксными условиями могут сделать ваш код намного более читабельным.
Оператор last немедленно завершает цикл. Чтобы закончить обработку файла, как только вы достигли признака завершения, напишите:
while (<$fh>)
{
next if /\A#/;
last if /\A__END__/
...
}
Оператор redo перезапускает текущую итерацию без повторного вычисления условия. Это может быть полезно в тех немногих случаях, когда вы хотите сразу же изменить прочитанную строку, а затем начать обработку с начала, не затирая её следующей строкой. Так можно реализовать простой парсер файла, который объединяет строки, завершающиеся обратным слешем:
while (my $line = <$fh>)
{
chomp $line;
# поиск обратного слеша в конце строки
if ($line =~ s{\\$}{})
{
$line .= <$fh>;
chomp $line;
redo;
}
...
}
Использование операторов контроля цикла во вложенных списках может сбивать с толку. Если вы не можете избежать вложенных циклов — выделив внутренние циклы в именованные функции — используйте метку цикла для прояснения:
LINE:
while (<$fh>)
{
chomp;
PREFIX:
for my $prefix (@prefixes)
{
next LINE unless $prefix;
say "$prefix: $_";
# здесь подразумевается next PREFIX
}
}
Конструкция continue
ведёт себя как третье подвыражение цикла for
; Perl выполняет её блок перед следующей итерацией цикла, как при нормальном повторении цикла, так и при преждевременном повторе итерации с помощью next
(footnote: Эквивалент continue
языка C в Perl — это next
.). Вы можете использовать её с циклами while
, until
, when
или for
. Примеры употребления
while ($i < 10 )
{
next unless $i % 2;
say $i;
}
continue
{
say 'Continuing...';
$i++;
}
Имейте ввиду, что блок continue
не выполняется, когда поток управления выходит из цикла вследствие срабатывания last
или redo
.
Конструкция given
— новая возможность Perl 5.10. Она присваивает значение выражения переменной-топику и предваряет блок:
given ($name) { ... }
В отличие от for
, она не итерирует по агрегатной переменной. Она вычисляет своё выражение в скалярном контексте и всегда осуществляет присваивание переменной-топику:
given (my $username = find_user())
{
is( $username, $_, 'topic auto-assignment' );
}
given
также локализует переменную-топик:
given ('mouse')
{
say;
mouse_to_man( $_ );
say;
}
sub mouse_to_man { s/mouse/man/ }
given
наиболее полезна при использовании совместно с when
(Умное сопоставление). given
топикализирует значение внутри блока, так что несколько инструкций when
могут проверять соответствие топика выражениям, используя семантику умного сопоставления. Так можно написать игру «камень, ножницы, бумага».
my @options = ( \&rock, \&paper, \&scissors );
my $confused = "I don't understand your move.";
do
{
say "Rock, Paper, Scissors! Pick one: ";
chomp( my $user = <STDIN> );
my $computer_match = $options[ rand @options ];
$computer_match->( lc( $user ) );
} until (eof);
sub rock
{
print "I chose rock. ";
given (shift)
{
when (/paper/) { say 'You win!' };
when (/rock/) { say 'We tie!' };
when (/scissors/) { say 'I win!' };
default { say $confused };
}
}
sub paper
{
print "I chose paper. ";
given (shift)
{
when (/paper/) { say 'We tie!' };
when (/rock/) { say 'I win!' };
when (/scissors/) { say 'You win!' };
default { say $confused };
}
}
sub scissors
{
print "I chose scissors. ";
given (shift)
{
when (/paper/) { say 'I win!' };
when (/rock/) { say 'You win!' };
when (/scissors/) { say 'We tie!' };
default { say $confused };
}
}
Perl выполняет правило default
, если ни одно из других условий не подошло.
CPAN-модуль MooseX::MultiMethods
предоставляет другую технику для упрощения этого кода.
Хвостовой вызов происходит, когда последнее выражение в функции — вызов другой функции: возвращаемое значение внутренней функции будет возвращаемым значением внешней функции:
sub log_and_greet_person
{
my $name = shift;
log( "Greeting $name" );
return greet_person( $name );
}
Возврат из green_person()
напрямую в код, вызывающий log_and_greet_person()
, более эффективен, чем возврат в log_and_greet_person()
и сразу же возврат из log_and_greet_person()
. Возврат напрямую из greet_person()
в код, вызывающий log_and_greet_person()
— оптимизация хвостового вызова.
Код с тяжёлой рекурсией (Рекурсия), особенно взаимнорекурсивный код, может потреблять много памяти. Хвостовые вызовы уменьшают количество памяти, требуемое для внутренней организации потока управления, и делают дорогие алгоритмы легко обрабатываемыми. К сожалению, Perl 5 не выполняет эту автоматизацию автоматически; вы должны сделать это сами, если необходимо.
Встроенный оператор goto
имеет форму, которая вызывает функцию, как если бы текущая функция никогда не была вызвана, по существу, обнуляя управление ресурсами для вызова новой функции. Уродливый синтаксис смущает людей, на слуху у которых «никогда не используйте goto
», но это работает:
sub log_and_greet_person
{
my ($name) = @_;
log( "Greeting $name" );
goto &greet_person;
}
Этот пример имеет две важных особенности. Во-первых, goto &function_name
или goto &$function_reference
требует использования сигила функции (&
), чтобы парсер знал, что нужно выполнить хвостовой вызов вместо перехода к метке. Во-вторых, эта форма вызова функции неявно передаёт содержимое @_
в вызываемую функцию. Вы можете модифицировать @_
для изменения передаваемых аргументов.
Эта техника относительно редка; она наиболее полезна, когда вы хотите вмешаться в поток управления, чтобы убраться с пути других функций, проверяющих caller
(например, если вы реализуете специальное логирование или какую-нибудь возможность отладки), или когда используете алгоритм, требующий большого количества рекурсии.
Основной тип данных Perl 5 — это скаляр, одиночное, отдельное значение. Значение может быть строкой, целым числом, значением с плавающей точкой, дескриптором файла или ссылкой — но это всегда одиночное значение. Скаляры могут быть лексическими переменными, переменными пакета или глобальными (Глобальные переменные) переменными. Вы можете объявлять только лексические переменные и переменные пакетов. Имена скалярных переменных должны соответствовать стандартным правилам именования переменных (Имена). Скалярные переменные всегда начинаются с сигила (Сигилы переменных) в виде знака доллара ($
).
Скалярные значения и скалярный контекст имеют глубокую связь; присваивание скаляру создаёт скалярный контекст. Использование скалярного сигила с агрегатной переменной налагает скалярный контекст для доступа к единственному элементу хеша или массива.
Скалярная переменная может содержать любой тип скалярного значения без специальной конвертации или приведения, и тип значения, сохранённого в переменной, может меняться:
my $value;
$value = 123.456;
$value = 77;
$value = "I am Chuck's big toe.";
$value = Store::IceCream->new();
Хотя этот код допустим, изменение типа данных, сохраняемых в скаляре, — признак беспорядка.
Эта гибкость типов часто приводит к приведению типа значения (Приведение типов). Например, вы можете обращаться с содержимым скаляра как со строкой, даже если вы явно не назначали ему строку:
my $zip_code = 97006;
my $city_state_zip = 'Beaverton, Oregon' . ' ' . $zip_code;
Также вы можете производить математические операции над строками:
my $call_sign = 'KBMIU';
# обновить знак и вернуть новое значение
my $next_sign = ++$call_sign;
# вернуть старое значение, затем обновить знак
my $curr_sign = $call_sign++;
# но не работает в таком виде:
my $new_sign = $call_sign + 1;
Это магическое поведение инкремента строки не имеет соответствующего магического поведения декремента. Вы не сможете получить предыдущее значение строки, написав $call_sign--
.
Эта операция инкремента строки превращает a
в b
и z
в aa
, учитывая набор символов и регистр. В то время как ZZ9
становится AAA0
, ZZ09
становится ZZ10
— числа проворачиваются до тех пор, пока есть большие значащие цифры для инкремента, как на автомобильном одометре.
Вычисление ссылки (Ссылки) в строковом контексте возвращает строку. Вычисление ссылки в числовом контексте возвращает число. Ни одна из этих операций не модифицирует ссылку, но вы не сможете восстановить ссылку из любого из результатов:
my $authors = [qw( Pratchett Vinge Conway )];
my $stringy_ref = '' . $authors;
my $numeric_ref = 0 + $authors;
$authors
— всё ещё пригодная к употреблению ссылка, но $stringy_ref
— строка, не имеющая никакой связи со ссылкой, и $numeric_ref
— число, также не имеющее никакой связи со ссылкой.
Чтобы обеспечить возможность преобразования без потери данных, скаляры Perl 5 могут содержать и числовой, и строковый компоненты. Внутренняя структура данных, представляющая скаляр в Perl 5, имеет числовой слот и строковый слот. Доступ к строке в числовом контексте производит скаляр и со строковым, и с числовым значениями. Функция dualvar()
базового модуля Scalar::Util
позволяет вам манипулировать обоими значениями напрямую в одном скаляре.
Скаляры не имеют отдельного слота для булевых значений. В булевом контексте пустая строка (''
) и '0'
— ложны. Все остальные строки — истинны. В булевом контексте числа, равные нулю (0
, 0.0
и 0e0
), ложны. Все остальные числа — истинны.
Обратите внимание, что строки '0.0'
и '0e0'
истинны; это одно из мест, где Perl 5 делает различие между тем, что выглядит как число, и что на самом деле число.
Ещё одно значение всегда ложно: undef
. Это значение неинициализированных переменных, кроме того, оно может быть и самостоятельным значением.
Массивы в Perl 5 — это структуры данных первого класса — язык поддерживает их как встроенный тип данных — которые хранят ноль или больше скаляров. Вы можете получить доступ к отдельным элементам массива по целочисленным индексам, а также можете по желанию добавлять или удалять элементы. Сигил @
обозначает массив. Объявить массив можно так:
my @items;
Доступ к отдельному элементу массива в Perl 5 требует использования скалярного сигила. $cats[0]
— однозначное использование массива @cats
, потому что постфиксные (Фиксность) квадратные скобки ([]
) всегда означают доступ к массиву по индексу.
Первый элемент массива имеет индекс ноль:
# @cats содержит список объектов Cat
my $first_cat = $cats[0];
Последний индекс массива зависит от количества элементов в нём. Массив в скалярном контексте (в скалярном присваивании, строковой конкатенации, сложении или булевом контексте) возвращает количество элементов в массиве:
# скалярное присваивание
my $num_cats = @cats;
# строковая конкатенация
say 'I have ' . @cats . ' cats!';
# сложение
my $num_animals = @cats + @dogs + @fish;
# булев контекст
say 'Yep, a cat owner!' if @cats;
Чтобы получить индекс последнего элемента массива, вычтите единицу из количества элементов в массиве (помните, что индексация массива начинается с 0) или испольуйте неуклюжий синтаксис $#cats
:
my $first_index = 0;
my $last_index = @cats - 1;
# or
# my $last_index = $#cats;
say "My first cat has an index of $first_index, "
. "and my last cat has an index of $last_index."
Когда индекс менее важен, чем позиция элемента, используйте взамен отрицательные индексы. Последний элемент массива доступен по индексу -1
. Второй с конца элемент доступен по индексу -2
и т. д.:
my $last_cat = $cats[-1];
my $second_to_last_cat = $cats[-2];
$#
имеет другое применение: изменение размера массива путём присваивания ему. Помните, что массивы в Perl 5 могут изменяться. Они расширяются или сокращаются по необходимости. Если вы сжимаете массив, Perl отбросит значения, не помещающиеся в массив с изменённым размером. Если вы расширяете массив, Perl заполнит добавленные позиции значением undef
.
Присваивайте отдельной позиции в массиве напрямую, используя индекс:
my @cats;
$cats[3] = 'Jack';
$cats[2] = 'Tuxedo';
$cats[0] = 'Daisy';
$cats[1] = 'Petunia';
$cats[4] = 'Brad';
$cats[5] = 'Choco';
Если вы присвоите индексу, выходящему за пределы текущего размера массива, Perl расширит массив для соответсвия новому размеру и заполнит все промежуточные позиции значением undef
. После первого присвоения массив будет содержать undef
в позициях 0, 1 и 2, и Jack
в позиции 3.
Для сокращения присваивания, инициализируйте массив из списка:
my @cats = ( 'Daisy', 'Petunia', 'Tuxedo', ... );
…но помните, что эти скобки не создают список. Без скобок произошло бы присваивание Daisy
первому и единственному элементу массива, ввиду приоритета операторов (Приоритет).
Любое выражение, возвращающее список в списочном контексте, может быть присвоено массиву:
my @cats = get_cat_list();
my @timeinfo = localtime();
my @nums = 1 .. 10;
Присваивание скалярному элементу массива налагает скалярный контекст, тогда как присваивание всему массиву целиком налагает списочный контекст.
Чтобы очистить массив, присвойте ему пустой список:
my @dates = ( 1969, 2001, 2010, 2051, 1787 );
...
@dates = ();
my @items = ();
— более длинная и зашумлённая версия my @items
, потому что свежеобъявленные массивы создаются пустыми.
Иногда массив удобнее использовать как упорядоченную, изменяемую коллекцию элементов, чем соответствие индексов значениям. Perl 5 предоставляет несколько операций для манипулирования элементами массива без использования индексов:
Операторы push
и pop
добавляют и удаляют элементы из конца массива соответственно:
my @meals;
# что есть съедобного?
push @meals, qw( hamburgers pizza lasagna turnip );
# …но ваш племянник терпеть не может овощи
pop @meals;
Вы можете добавить в массив список значений с помощью push
, но с помощью pop
можете удалить только один за раз. push
возвращает новое количество элементов в массиве. pop
возвращает удалённый элемент.
Поскольку push
оперирует списками, вы легко можете добавить элементы одного или нескольких массивов в другой:
push @meals, @breakfast, @lunch, @dinner;
Аналогично, unshift
и shift
добавляют элементы или удаляют элемент из начала массива соответственно:
# расширить наши кулинарные горизонты
unshift @meals, qw( tofu spanakopita taquitos );
# пересмотрим всю эту идею с соей
shift @meals;
unshift
вставляет список элементов в начало массива и возвращает новое количество элементов в массиве. shift
удаляет и возвращает первый элемент массива.
Немногие программы используют возвращаемые значения push
и unshift
.
Оператор splice
удаляет и заменяет элементы в массиве в соответствии с заданным смещением, длинной вырезаемого списка и списком для замены. И замена, и удаление — опциональны; вы можете опустить любое из этих поведений. Описание splice
в perlfunc
демонстрирует его равноценность с push
, pop
, shift
и unshift
. Одно из возможных применений — удаление двух элементов из массива:
my ($winner, $runnerup) = splice @finalists, 0, 2;
# или
my $winner = shift @finalists;
my $runnerup = shift @finalists;
До Perl 5.12 итерация по массиву с использованием индекса требовала цикла в стиле C. Начиная с Perl 5.12, each
может итерировать по массиву по индексу и значению:
while (my ($index, $value) = each @bookshelf)
{
say "#$index: $value";
...
}
Срез массива позволяет вам получить доступ к элементам массива в списочном контексте. В отличие от скалярного доступа к элементу массива, эта операция принимает список из ноля или более индексов и использует сигил массива (@
):
my @youngest_cats = @cats[-1, -2];
my @oldest_cats = @cats[0 .. 2];
my @selected_cats = @cats[ @indexes ];
Срезы массивов удобны для присваивания:
@users[ @replace_indices ] = @replace_users;
Срез может содержать ноль или более элементов — включая один:
# одноэлементный срез массива; списочный контекст
@cats[-1] = get_more_cats();
# доступ к одному элементу массива; скалярный контекст
$cats[-1] = get_more_cats();
Единственное синтаксическое различие между срезом массива из одного элемента и скалярным доступом к элементу массива — предшествующий сигил. Семантическая разница намного больше: срез массива всегда налагает списочный контекст. Срез массива, используемый в скалярном контексте, вызовет предупреждение:
Scalar value @cats[1] better written as $cats[1]...
Срез массива налагает списочный контекст на выражение, используемое в качестве его индекса:
# функция вызывается в списочном контексте
my @hungry_cats = @cats[ get_cat_indices() ];
В списочном контексте массивы разглаживаются в списки. Если вы передадите несколько массивов нормальной функции в Perl 5, они разгладятся в единый список:
my @cats = qw( Daisy Petunia Tuxedo Brad Jack );
my @dogs = qw( Rodney Lucky );
take_pets_to_vet( @cats, @dogs );
sub take_pets_to_vet
{
# ГЛЮЧНО: не использовать!
my (@cats, @dogs) = @_;
...
}
Внутри функции @_
будет содержать семь элементов, не два, потому что списочное присваивание массивам обладает жадностью. Массив поглотит столько элементов из списка, сколько возможно. После присваивания @cats
будет содержать все аргументы, переданные в функцию. @dogs
будет пустым.
Это разглаживающиее поведение иногда ставит в тупик новичков, которые пытаются создать вложенные массивы в Perl 5:
# создаёт один массив, не массив массивов
my @numbers = ( 1 .. 10,
( 11 .. 20,
( 21 .. 30 ) ) );
…но этот код, в сущности, то же самое, что и:
# создаёт один массив, не массив массивов
my @numbers = 1 .. 30;
…потому что скобки всего лишь группируют выражения. Они не создают списки в этой ситуации. Чтобы избежать этого разглаживающего поведения, используйте ссылки на массивы (Ссылки на массивы).
Массивы интерполируются в строках как списки всех приведённых в строковый вид элементов, разделённых текущим значением магической глобальной переменной $"
. Значение по умолчанию этой переменной — один пробел. Её мнемоника в English.pm — $LIST_SEPARATOR
. Поэтому получаем следующее:
my @alphabet = 'a' .. 'z';
say "[@alphabet]";
[a b c d e f g h i j k l m
n o p q r s t u v w x y z]
Локализуйте $"
своим разделителем чтобы облегчить отладку (footnote: Благодарность за эту технику уходит Марку Джейсону Доминусу (Mark Jason Dominus).):
# так что там в этом массиве?
local $" = ')(';
say "(@sweet_treats)";
(pie)(cake)(doughnuts)(cookies)(raisin bread)
Хеш — структура данных первого класса в Perl, которая ассоциирует строковые ключи со скалярными значениями. Так же как имя переменной соотносится с местом хранения, так и ключ в хеше ссылается на значение. Воспринимайте хеш как телефонную книгу: используйте имена ваших друзей для поиска их номеров. В других языках хеши называются таблицами, ассоциативными массивами, словарями или картами.
Хеши имеют два важных свойства: они хранят один скаляр на уникальный ключ, и они не имеют определённой сортировки ключей.
Хеши используют сигил %
. Объявляются хеши так:
my %favorite_flavors;
Хеши создаются пустыми. Вы можете написать my %favorite_flavors = ()
, но это излишне.
Хеши используют скалярный сигил $
для доступа к отдельным элементам и фигурные скобки { }
для доступа по ключам:
my %favorite_flavors;
$favorite_flavors{Gabi} = 'Raspberry chocolate';
$favorite_flavors{Annette} = 'French vanilla';
Присваивание хешу списка ключей и значений в одном выражении:
my %favorite_flavors = (
'Gabi', 'Raspberry chocolate',
'Annette', 'French vanilla',
);
Если вы присвоите нечётное количество элементов хешу, то получите предупреждение. Идиоматический Perl часто использует оператор толстая запятая (=>
) для установки соответствия значений ключам, так как это делает разбиение на пары более заметным визуально:
my %favorite_flavors = (
Gabi => 'Mint chocolate chip',
Annette => 'French vanilla',
);
Оператор толстая запятая ведёт себя как обычная запятая, но кроме того он автоматически заключает в кавычки предшествующее голое слово (Голые слова). Прагма strict
не будет предупреждать о таком голом слове — и если у вас есть функция с таким же именем, как ключ хеша, толстая запятая не будет вызывать функцию:
sub name { 'Leonardo' }
my %address =
(
name => '1123 Fib Place',
);
Ключом хеша будет name
, а не Leonardo
. Чтобы вызвать функцию, сделайте её вызов явным:
my %address =
(
name() => '1123 Fib Place',
);
Для очистки хеша присвойте ему пустой список (footnote: Иногда вам может встретиться undef %hash
.):
%favorite_flavors = ();
Доступ к отдельному значению хеша осуществляется с помощью операции индексации. Используйте ключ (доступ по ключу) для получения значения из хеша:
my $address = $addresses{$name};
В этом примере $name
содержит строку, которая также является ключом хеша. Как и при доступе к отдельному элементу массива, сигил хеша изменился с %
на $
для обозначения доступа по ключу к скалярному значению.
Вы можете использовать строковые литералы как ключи хеша. Perl автоматически заключит голые слова в кавычки в соответствии с теми же правилами, что и толстые запятые:
# автоматическое заключение в кавычки
my $address = $addresses{Victor};
# требует заключения в кавычки; не допустимое голое слово
my $address = $addresses{'Sue-Linn'};
# вызов функции требует устранения неоднозначности
my $address = $addresses{get_name()};
Новички зачастую всегда заключают в кавычки строковые литералы, являющиеся ключами хеша, но опытные разработчики опускают кавычки везде, где возможно. В этом смысле, наличие кавычек в ключах хеша указывает на намерение сделать что-то другое.
Даже на встроенные функции Perl 5 распространяется автоматическое заключение в кавычки:
my %addresses =
(
Leonardo => '1123 Fib Place',
Utako => 'Cantor Hotel, Room 1',
);
sub get_address_from_name
{
return $addresses{+shift};
}
Унарный плюс (Унарное приведение типа) превращает то, на что как на голое слово (shift
) должны распространяться правила автоматического заключения в кавычки, в выражение. Как из этого следует, вы можете использовать произвольное выражение — не только вызов функции — как ключ хеша:
# хотя не стоит этого делать на самом деле
my $address = $addresses{reverse 'odranoeL'};
# интерполяция допустима
my $address = $addresses{"$first_name $last_name"};
# как и вызовы методов
my $address = $addresses{ $user->name() };
Ключи хешей могут быть только строками. Всё, что вычисляется в строку, является допустимым ключом хеша. Perl пойдёт так далеко, что приведёт (Приведение типов) любую не-строку в строку, так что если вы используете объект как ключ хеша, вы получите строковую версию этого объекта вместо самого объекта:
for my $isbn (@isbns)
{
my $book = Book->fetch_by_isbn( $isbn );
# вряд ли сделает именно то, чего вы хотите
$books{$book} = $book->price;
}
Оператор exists
возвращает булево значение, указывающее, содержит ли хеш указанный ключ:
my %addresses =
(
Leonardo => '1123 Fib Place',
Utako => 'Cantor Hotel, Room 1',
);
say "Have Leonardo's address"
if exists $addresses{Leonardo};
say "Have Warnie's address"
if exists $addresses{Warnie};
Использование exists
вместо прямого доступа к ключу хеша позволяет избежать двух проблем. Во-первых, оно не проверяет булеву природу значения хеша; ключ хеша может существовать и иметь значение, даже если это значение является ложью (включая undef
):
my %false_key_value = ( 0 => '' );
ok( %false_key_value,
'hash containing false key & value
should evaluate to a true value' );
Во-вторых, exists
избегает автовивификации (Автовивификация) во вложенных структурах данных (Вложенные структуры данных).
Если ключ хеша существует, его значением может быть undef
. Проверьте это с помощью defined
:
$addresses{Leibniz} = undef;
say "Gottfried lives at $addresses{Leibniz}"
if exists $addresses{Leibniz}
&& defined $addresses{Leibniz};
Хеши — агрегатные переменные, но их парная природа даёт намного больше возможностей для итераций: по ключам хеша, по значениям хеша, или по парам ключей и значений. Оператор keys
возвращает список ключей хеша:
for my $addressee (keys %addresses)
{
say "Found an address for $addressee!";
}
Оператор values
возвращает список значений хеша:
for my $address (values %addresses)
{
say "Someone lives at $address";
}
Оператор each
возвращает список двухэлементных списков из ключа и значения:
while (my ($addressee, $address) = each %addresses)
{
say "$addressee lives at $address";
}
В отличие от массивов, в этих списках нет очевидной сортировки. Сортировка зависит от внутренней реализации хеша, конкретной используемой вами версии Perl, размера хеша и элемента случайности. Несмотря на это, порядок элементов хеша согласован для keys
, values
и each
. Модификация хеша может изменять этот порядок, но вы можете на него полагаться, если хеш остаётся тем же самым.
Каждый хеш имеет тольк один итератор для оператора each
. Вы не можете надёжно итерировать по хешу с помощью each
более одного раза; если вы начнёте новую итерацию, когда другая ещё не завершена, предыдущая преждевременно закончится, а последующая начнётся с середины хеша. Во время таких итераций остерегайтесь вызывать какие-либо функции, которые сами могут итерировать по хешу посредством each
.
На практике такое случается редко, но если вам это нужно, сбросить итератор хеша можно с помощью keys
или values
в пустом контексте:
# сброс итератора хеша
keys %addresses;
while (my ($addressee, $address) = each %addresses)
{
...
}
Срез хеша — это список ключей и значений хеша, индексированных в одной операции. Так можно инициализировать несколько элементов хеша сразу:
# %cats уже содержит элементы
@cats{qw( Jack Brad Mars Grumpy )} = (1) x 4;
Это эквивалентно следующей инициализации:
my %cats = map { $_ => 1 }
qw( Jack Brad Mars Grumpy );
…за исключением того, что инициализация среза хеша не заменяет существующее содержимое хеша.
Срезы хеша также позволяют вам получить несколько значений из хеша в одной операции. Как и в случае срезов массива, сигил хеша изменяется для обозначения списочного контекста. Использование фигурных скобок указывает на доступ по ключу и делает хеш однозначным:
my @buyer_addresses = @addresses{ @buyers };
Срезы хешей облегчают слияние двух хешей:
my %addresses = ( ... );
my %canada_addresses = ( ... );
@addresses{ keys %canada_addresses }
= values %canada_addresses;
Это эквивалентно ручному циклу по содержимому %canada_addresses
, но намного короче.
Что если один и тот же ключ имеет место в обоих хешах? Подход со срезом хеша всегда перезаписывает существующие пары ключ/значение в %addresses
. Если вам нужно другое поведение, больше подойдут циклы.
Пустой хеш не содержит ни ключей, ни значений. Он даёт ложное значение в булевом контексте. Хеш, содержащий хотя бы одну пару ключ/значение, в булевом контексте даёт истинное значение, даже если все ключи, или все значения, или и то и другое, сами по себе в булевом контексте являются ложными значениями.
use Test::More;
my %empty;
ok( ! %empty, 'empty hash should evaluate false' );
my %false_key = ( 0 => 'true value' );
ok( %false_key, 'hash containing false key
should evaluate to true' );
my %false_value = ( 'true key' => 0 );
ok( %false_value, 'hash containing false value
should evaluate to true' );
done_testing();
В скалярном контексте хеш возвращает строку, представляющую соотношение полных корзин в хеше — внутренние детали реализации хешей, которые вы смело можете игнорировать.
В списочном контексте хеш возвращает список пар ключ/значение, подобный тому, что вы получаете от оператора each
. Однако вы не можете итерировать по этому списку так же, как итерируете по списку, выдаваемому each
, иначе цикл никогда не прервётся:
# бесконечный цикл для непустых хешей
while (my ($key, $value) = %hash)
{
...
}
Вы можете осуществлять цикл по ключам и значениям с помощью цикла for
, но переменная-итератор будет получать ключ на одной итерации и его значение на следующей, потому что Perl разгладит хеш в один список перемежающихся ключей и значений.
Поскольку каждый ключ в хеше встречается только один раз, присваивание того же самого ключа хешу несколько раз сохраняет только самый последний ключ. Используйте это для нахождения уникальных элементов списка:
my %uniq;
undef @uniq{ @items };
my @uniques = keys %uniq;
Использование undef
со срезом хеша устанавливает значения хеша в undef
. Эта идиома — самый дешёвый способ выполнить операции установки значения с хешем.
Хеши также полезны для подсчёта элементов, таких как IP-адреса в лог-файле:
my %ip_addresses;
while (my $line = <$logfile>)
{
my ($ip, $resource) = analyze_line( $line );
$ip_addresses{$ip}++;
...
}
Начальное значение значений хеша — undef
. Оператор постфиксного инкремента (++
) воспринимает его как ноль. Модификация значения инкрементирует существующее значения для этого ключа. Если значения для этого ключа не существует, Perl создаёт значение (undef
) и немедленно инкрементирует его до единицы, так как преобразование undef
в число даёт значение 0.
Эта стратегия обеспечивает полезный механизм кеширования для сохранения результата дорогих операций с небольшим оверхедом:
{
my %user_cache;
sub fetch_user
{
my $id = shift;
$user_cache{$id} //= create_user($id);
return $user_cache{$id};
}
}
Этот орочий манёвр (footnote: Or-cache, orcish, если вы любите игру слов.) возвращает значение из хеша, если оно существует. В противном случае, он вычисляет, кеширует и возвращает значение. Оператор определено-или-присвоить (//=
) вычисляет свой левый операнд. Если этот операнд не определён, оператор присваивает левому значению значение своего правого операнда. Другими словами, если в хеше нет значения для заданного ключа, эта функция вызовет create_user()
с этим ключом и обновит хеш.
Операторы определено-или и определено-или-присвоить были представлены в Per 5.10. До 5.10 большая часть кода использовала оператор или-присвоить (||=
) для этой цели. К сожалению, некоторые валидные значения дают ложное значение в булевом контексте, так что вычисление определённости значений почти всегда более правильно. Этот ленивый орочий манёвр проверяет определённость кешированного значения, не истинность.
Если ваша функция принимает несколько аргументов, используйте захватывающий хеш (Проглатывание) для сбора пар ключ/значение в один хеш как именованных аргументов функции:
sub make_sundae
{
my %parameters = @_;
...
}
make_sundae( flavor => 'Lemon Burst',
topping => 'cookie bits' );
Этот подход позволяет вам устанавливать значения по умолчанию:
sub make_sundae
{
my %parameters = @_;
$parameters{flavor} //= 'Vanilla';
$parameters{topping} //= 'fudge';
$parameters{sprinkles} //= 100;
...
}
…или включать их в инициализацию хеша, так как последующее присваивание перезаписывает предыдущие:
sub make_sundae
{
my %parameters =
(
flavor => 'Vanilla',
topping => 'fudge',
sprinkles => 100,
@_,
);
...
}
Так как ключи хешей — голые слова, они мало защищены от опечаток по сравнению с защитой имён функций и переменных, предлагаемой прагмой strict
. Редко используемый базовый модуль Hash::Util
предоставляет механизмы для улучшения этой ситуации.
Чтобы не дать кому-либо случайно добавить ключ хеша, который вы не намеревались (в случае опечатки или из ненадёжного пользовательского ввода), используйте функцию lock_keys()
чтобы ограничить хеш его текущим набором ключей. Любая попытка добавить новый ключ в хеш выбросит исключение. Эта слабая мера безопасности годится только для предотвращения случайностей; кто угодно может использовать функцию unlock_keys()
чтобы удалить эту защиту.
Аналогично вы можете заблокировать или разблокировать существующее значение для заданного ключа хеша (lock_value()
и unlock_value()
) и сделать весь хеш доступным только для чтения или лишить его этого свойства с помощью lock_hash()
и unlock_hash()
.
Переменная в Perl может содержать в разное время значения разных типов — строки, целые и рациональные числа и т. д. Вместо присоединения информации о типе к переменным, Perl полагается на контекст, устанавливаемый операторами (Числовой, строковый и булев контекст) для понимания того, что делать со значениями. По своему дизайну, Perl пытается делать то, что вы имеете ввиду (footnote: Это называется DWIM, do what i mean (делай то, что я имею ввиду), или DWIM-ность.), хотя вы должны чётко выражать свои намерения. Если вы обращаетесь с переменной, содержащей число, как со строкой, Perl сделает всё, что сможет, для приведения типа этого числа к строке.
Булево приведение типов происходит, когда вы проверяете истинность значения, как, например, в условиях if
или while
. Числовой 0, '0'
являются ложными значениями. Все остальные значения — включая строки, которые могут быть численно равными нулю (такие как '0.0'
, '0e'
и '0 but true'
), — являются ложными.
Если скаляр имет оба компонента (Двойные переменные), и строковый, и числовой, Perl 5 предпочтёт проверить на булеву истину строковый компонент. '0 but true'
численно вычисляется в ноль, но это не пустая строка, поэтому в булевом контексте она вычисляется в истинное значение.
Строковое приведение типов происходит при использовании строковых операторов, таких как сравнение (eq
и cmp
), конкатенация, split
, substr
и регулярные выражения, а также при использовании значения как ключа хеша. Неопределённое значение преобразуется в пустую строку, выдавая предупреждение «use of uninitialized value». Числа преобразуются в строки, содержащие их значения, то есть значение 10
преобразуется в строку 10
. Вы можете даже разделить число на отдельные цифры с помощью split
:
my @digits = split '', 1234567890;
Числовое приведение типов происходит при использовании операторов числового сравнения (таких как ==
и <=>
), при выполнении математических операций и при использовании значения как индекса массива или списка. Неопределённое значение преобразуется в ноль и выдаёт предупреждение «use of uninitialized value». Строки, которые не начинаются с числовой части, тоже преобразуются в ноль и выдают предупреждение «Argument isn't numeric». Строки, начинающиеся с символов, разрешённых в числовых литералах, преобразуются в эти значения, не выдавая предупреждений, то есть 10 leptons leaping
преобразуется в 10
, а 6.022e23 moles marauding
— в 6.022e23
.
Базовый модуль Scalar::Util
содержит функцию looks_like_number()
, которая использует те же правила разбора, что и грамматика Perl 5, для выделения числа из строки.
Строки Inf
и Infinity
обозначают бесконечное значение и ведут себя как числа. Строка NaN
обозначает понятие «не число». Их преобразование в числа не выдаёт предупреждения «Argument isn't numeric».
Использование операции разыменования ссылки на значении, не являющемся ссылкой, превращает его в ссылку. Этот процесс автовивификации (Автовивификация) удобен при манипулировании вложенными структурами данных (Вложенные структуры данных):
my %users;
$users{Brad}{id} = 228;
$users{Jack}{id} = 229;
Хотя хеш не содержал значений для ключей Brad
и Jack
, Perl создаёт для них ссылки на хеши и присваивает каждому пару ключ/значение с ключом id
.
Внутреннее представление значений в Perl 5 хранит и строковое, и числовое значения. Преобразование числа в строку не заменяет числовое значение. Вместо этого оно присоединяет преобразованное в строку значение, так что представление будет содержать оба компонента. Аналогично, преобразование строки в число заполняет числовой компонент, оставляя строковый компонент нетронутым.
Некоторые операции в Perl предпочитают использование одного из компонентов другому — булева проверка, например, предпочитает строки. Если значение имеет кешированное представление в форме, которую вы не ожидаете, расчёт на неявную конвертацию может привести к неожиданным результатам. Вам почти никогда не нужно будет явно указывать, что вы ожидаете (footnote: Автор может припомнить два таких случая за более чем десятилетие программирования на Perl 5), но знание того, что это кеширование происходит, может однажды помочь вам диагностировать странную ситуацию.
Многокомпонентная природа переменных Perl доступна пользователям в виде двойных переменных. Базовый модуль Scalar::Util
предоставляет функцию dualvar()
, которая позволяет вам обойти приведение типов Perl и манипулировать строковым и числовым компонентами значения по отдельности:
use Scalar::Util 'dualvar';
my $false_name = dualvar 0, 'Sparkles & Blue';
say 'Boolean true!' if !! $false_name;
say 'Numeric false!' unless 0 + $false_name;
say 'String true!' if '' . $false_name;
Пространство имён в Perl объединяет и инкапсулирует разные именованные сущности в одной именованной категории, как ваша фамилия или название бренда. В отличие от имён в реальном мире, пространство имён не подразумевает никаких непосредственных взаимосвязей между сущностями. Такие взаимосвязи могут существовать, но не обязаны.
Пакет в Perl 5 — это набор кода в едином пространстве имён. Имеется тонкое различие: пакет представляет собой исходный код, а пространство имён — сущность, создаваемую, когда Perl парсит этот код.
Встроенная директива package
объявляет пакет и пространство имён:
package MyCode;
our @boxes;
sub add_box { ... }
Все глобальные переменные, которые объявляются или на которые ссылаются после объявления пакета, ссылаются на символы в пространстве имён MyCode
. Вы можете ссылаться на переменную @boxes
из пространства имён main
только по её полностью определённому имени @MyCode::boxes
. Полностью определённое имя включает полное имя пакета, так что вы можете вызвать функцию add_box()
только как MyCode::add_box()
.
Область видимости пакета продолжается до следующего объявления package
или до конца файла, что будет достигнуто раньше. Perl 5.14 усовершенствовал package
, так что теперь вы можете добавить блок, который явно очертит область видимости объявления:
package Pinball::Wizard
{
our $VERSION = 1969;
}
Пакет по умолчанию — main
. Без объявления пакета, текущим пакетом будет main
. Это правило распространяется на однострочники, автономные программы и даже файлы .pm.
Кроме имени пакет имеет версию и три неявных метода, import()
(Импорт), unimport()
и VERSION()
. VERSION()
возвращает номер версии пакета. Этот номер представляет собой последовательность чисел, содержащуюся в глобальной переменной пакета с именем $VERSION
. По примерному соглашению, версии имеют тенденцию быть последовательностью целых чисел, разделённых точками, как в 1.23
или 1.1.10
, где каждый сегмент — целое число.
Perl 5.12 представил новый синтаксис, предназначенный для упрощения номеров версий, как документировано в perldoc version::Internals
. Эти более строгие номера версий должны начинаться с символа v
и иметь как минимум три целочисленных компонента, разделённых точками:
package MyCode v1.2.1;
В Perl 5.14 опциональная блочная форма объявления package
выглядит так:
package Pinball::Wizard v1969.3.7
{
...
}
В 5.10 и раньше, самым простым способом объявить версию пакета следующий:
package MyCode;
our $VERSION = 1.21;
Каждый пакет наследует метод VERSION()
от базового класса UNIVERSAL
. Вы можете переопределить VERSION()
, хотя есть не так много причин это делать. Этот метод возвращает значение $VERSION
:
my $version = Some::Plugin->VERSION();
Если вы укажете номер версии в качестве аргумента, этот метод выбросит исключение, если версия модуля меньше указанного аргумента:
# требует как минимум 2.1
Some::Plugin->VERSION( 2.1 );
die "Your plugin $version is too old"
unless $version > 2;
Каждое объявление package
создаёт новое пространство имён, если необходимо, и заставляет парсер разместить все глобальные символы следующего затем пакета (глобальные переменные и функции) в этом пространстве имён.
В Perl открытые пространства имён. Вы можете добавлять функции или переменные в это пространство имён в любом месте, как с помощью нового объявления пакета:
package Pack
{
sub first_sub { ... }
}
Pack::first_sub();
package Pack
{
sub second_sub { ... }
}
Pack::second_sub();
…так и с помощью указания полностью определённых имён функций в местах объявления:
# подразумевается
package main;
sub Pack::third_sub { ... }
Вы можете делать добавления в пакет в любой точке во время компиляции или выполнения, независимо от текущего файла, хотя сборка пакета из нескольких отдельных объявлений может сделать код трудным для исследования.
Пространства имён могут иметь столь много уровней, сколько требует ваша организационная схема, хотя пространства имён — не иерархические. Единственная взаимосвязь между пакетами — семантическая, не техническая. Многие проекты и бизнесы создают свои собственные пространства имён верхнего уровня. Это уменьшает вероятность глобальных конфликтов и помогает организовать код на диске. Например:
StrangeMonkey
— имя проектаStrangeMonkey::UI
содержит код верхнего уровня для пользовательского интерфейсаStrangeMonkey::Persistence
содержит код верхнего уровня для управления даннымиStrangeMonkey::Test
содержит код верхнего уровня для тестирования проекта…и т. д.
Perl обычно делает то, чего вы ожидаете, даже если это требует проницательности. Посмотрите, что происходит, когда вы передаёте значения в функции:
sub reverse_greeting
{
my $name = reverse shift;
return "Hello, $name!";
}
my $name = 'Chuck';
say reverse_greeting( $name );
say $name;
Снаружи функции $name
содержит значение Chuck
, несмотря на то, что значение, переданное в функцию, переворачивается в kcuhC
. Вероятно, этого вы и ожидаете. Значение $name
снаружи функции отдельно от значения $name
внутри функции. Изменение одного не оказывает эффекта на другое.
Рассмотрим альтернативу. Если бы вам было необходимо делать копии каждого значения прежде чем что-либо могло бы изменить их независимо от вас, вам пришлось бы писать большое количество дополнительного защитного кода.
Однако иногда удобно модифицировать значения прямо на месте. Если вы хотите передать хеш, полный данных, в функцию, для изменения его, создание и возврат нового хеша для каждого изменения может быть хлопотным (уж не говоря о том, что неэффективным).
Perl 5 предоставляет механизм, позволяющий ссылаться на значение, не делая его копии. Любые изменения, сделанные с этой ссылкой, сразу же обновят значение, так что все ссылки на это значение смогут получить новое значение. Ссылка в Perl 5 — скалярный тип данных первого класса, который ссылается на другой тип данных первого класса.
Оператор взятия ссылки — обратный слеш (\
). В скалярном контексте он создаёт одиночную ссылку, ссылающуюся на другое значение. В списочном контексте он создаёт список ссылок. Чтобы взять ссылку от $name
:
my $name = 'Larry';
my $name_ref = \$name;
Вы должны разыменовать ссылку, чтобы получить значение, на которое она ссылается. Разыменовывание требует добавления дополнительного сигила для каждого уровня разыменовывания:
sub reverse_in_place
{
my $name_ref = shift;
$$name_ref = reverse $$name_ref;
}
my $name = 'Blabby';
reverse_in_place( \$name );
say $name;
Двойной скалярный сигил ($$
) разыменовывает ссылку на скаляр.
Находящиеся в @_
параметры ведут себя как псевдонимы переменных вызывающего кода (footnote: Вспомните, что цикл for
имеет аналогичное поведение в плане создания псевдонимов.), так что вы можете изменять их на месте:
sub reverse_value_in_place
{
$_[0] = reverse $_[0];
}
my $name = 'allizocohC';
reverse_value_in_place( $name );
say $name;
В большинстве случаев вы не захотите модифицировать значения таким способом — вызывающий код, например, редко этого ожидает. Присваивание параметров лексическим переменным внутри ваших функций избавляет от такого поведения.
Модификация значения на месте или возврат ссылки на скаляр может сэкономить память. Так как Perl копирует значения при присваивании, у вас может оказаться несколько копий большой строки. В случае передачи ссылок Perl будет копировать только ссылки — гораздо более дешёвая операция.
Сложные ссылки могут потребовать использования блока в фигурных скобках для разрешения неоднозначности частей выражения. Вы можете всегда использовать такой синтаксис, хотя иногда он проясняет, а иногда затеняет:
sub reverse_in_place
{
my $name_ref = shift;
${ $name_ref } = reverse ${ $name_ref };
}
Если вы забудете разыменовать ссылку на скаляр, Perl, вероятно, выполнит приведение типа ссылки. Строковое значение будет иметь вид SCALAR(0x93339e8)
, а числовое будет содержать часть 0x93339e8
. В этом значении зашифрован тип ссылки (в данном случае SCALAR
) и положение ссылки в памяти.
Perl не предоставляет доступа к ячейкам памяти. Адрес ссылки — это значение, используемое как идентификатор. В отличие от указателей в таких языках как C, вы не можете модифицировать адрес или обращаться с ним как с адресом в памяти. Эти адреса лишь в большинстве случаев уникальны, так как Perl может повторно использовать ячейки после освобождения неиспользуемой памяти.
Ссылки на массивы полезны в нескольких ситуациях:
Используйте оператор взятия ссылки для создания ссылки на объявленный массив:
my @cards = qw( K Q J 10 9 8 7 6 5 4 3 2 A );
my $cards_ref = \@cards;
Любые изменения, сделанные через $cards_ref
, будут изменять и @cards
, и наоборот. Вы можете получить доступ ко всему массиву целиком с помощью сигила @
, например, чтобы разгладить массив или сосчитать его элементы:
my $card_count = @$cards_ref;
my @card_copy = @$cards_ref;
Доступ к отдельным элементам осуществляется с помощью разыменовывающей стрелки (->
):
my $first_card = $cards_ref->[0];
my $last_card = $cards_ref->[-1];
Стрелка необходима, чтобы различать скаляр $cards_ref
и массив @cards_ref
. Обратите внимание на использование скалярного сигила (Сигилы переменных) для доступа к единственному элементу.
Альтернативный синтаксис добавляет ещё один скалярный сигил к ссылке на массив. Короче, хотя и уродливее, написать my $first_card = $$cards_ref[0];
.
Используйте разыменовывающий синтаксис с фигурными скобками для получения среза (Срезы массивов) по ссылке на массив:
my @high_cards = @{ $cards_ref }[0 .. 2, -1];
Вы можете опустить фигурные скобки, но их группировка часто улучшает читабельность.
Для создания анонимного массива — без использования объявленного массива — окружите список значений квадратными скобками:
my $suits_ref = [qw( Monkeys Robots Dinos Cheese )];
Эта ссылка на массив ведёт себя так же, как ссылки на именованные массивы, за исключением того, что скобки анонимного массива всегда создают новую ссылку. Ссылка, взятая на именованный массив, всегда ссылается на тот же самый массив с учётом области видимости. Например:
my @meals = qw( soup sandwiches pizza );
my $sunday_ref = \@meals;
my $monday_ref = \@meals;
push @meals, 'ice cream sundae';
…и $sunday_ref
, и $monday_ref
теперь содержат десерт, тогда как в следующем случае:
my @meals = qw( soup sandwiches pizza );
my $sunday_ref = [ @meals ];
my $monday_ref = [ @meals ];
push @meals, 'berry pie';
…ни $sunday_ref
, ни $monday_ref
не содержат десерт. В квадратных скобках, использованных для создания анонимного массива, списочный контекст разглаживает массив @meals
в список, не связанный с @meals
.
Используйте оператор взятия ссылки на именованном хеше для создания ссылки на хеш:
my %colors = (
black => 'negro',
blue => 'azul',
gold => 'dorado',
red => 'rojo',
yellow => 'amarillo',
purple => 'morado',
);
my $colors_ref = \%colors;
Доступ к ключам или значениям хеша можно получить, предварив ссылку сигилом хеша (%
):
my @english_colors = keys %$colors_ref;
my @spanish_colors = values %$colors_ref;
Доступ к отдельным значениям хеша (для сохранения, удаления, проверки существования или получения) осуществляется с использованием разыменовывающей стрелки или двойных сигилов:
sub translate_to_spanish
{
my $color = shift;
return $colors_ref->{$color};
# или return $$colors_ref{$color};
}
Используйте сигил массива (@
) и устраняющие неоднозначность скобки для получения среза по ссылке на хеш:
my @colors = qw( red blue green );
my @colores = @{ $colors_ref }{@colors};
Создание анонимного хеша с помощью фигурных скобок:
my $food_ref = {
'birthday cake' => 'la torta de cumpleaños',
candy => 'dulces',
cupcake => 'bizcochito',
'ice cream' => 'helado',
};
Как и анонимные массивы, анонимные хеши создают новый анонимный хеш при каждом выполнении.
Распространённая ошибка новичков — присваивание анонимного хеша стандартному хешу — выдаёт предупреждение о нечётном количестве элементов в хеше. Используйте круглые скобки для именованного хеша и фигурные скобки для анонимного хеша.
Начиная с Perl 5.14, Perl может автоматически разыменовать некоторые ссылки от вашего имени. Имея ссылку на массив в $arrayref
, вы можете написать:
push $arrayref, qw( list of values );
Имея выражение, возвращающее ссылку на массив, вы можете сделать то же самое:
push $houses{$location}[$closets], \@new_shoes;
То же относится и к операторам работы с массивами pop
, shift
, unshift
, splice
, keys
, values
, и each
, а также к операторам работы с хешами keys
, values
и each
.
Если предоставленная ссылка имеет несоответствующий тип — если она не разыменовывается должным образом — Perl выбросит исключение. Хотя это выглядит более опасным, чем явное разыменовывание ссылок напрямую, фактически, это то же самое поведение:
my $ref = sub { ... };
# выбросит исключение
push $ref, qw( list of values );
# тоже выбросит исключение
push @$ref, qw( list of values );
Perl 5 поддерживает функции первого класса, поскольку функция — такой же тип данных, как массив или хеш. Это наиболее очевидно в случае ссылок на функции и даёт много продвинутых возможностей (Замыкания). Ссылка на функцию создаётся посредством использования оператора взятия ссылки на имени функции:
sub bake_cake { say 'Baking a wonderful cake!' };
my $cake_ref = \&bake_cake;
Без сигила функции (&
), вы получите ссылку на возвращаемое значение или значения функции.
Анонимная функция создаётся с помощью голого ключевого слова sub
:
my $pie_ref = sub { say 'Making a delicious pie!' };
Использование встроенной директивы С без имени компилирует функцию, как обычно, но не помещает её в текущее пространство имён. Единственный способ получить доступ к этой функции — по ссылке, возвращённой sub
. Вызыв функции по ссылке осуществляется с помощью разыменовывающей стрелки:
$cake_ref->();
$pie_ref->();
Альтернативный синтаксис вызова для ссылок на функции использует сигил функции (&
) вместо разыменовывающей стрелки. Избегайте этого синтаксиса; он имеет специфические последствия для парсинга и передачи аргументов.
Воспринимайте пустые круглые скобки как обозначение операции разыменовывающего вызова, так же как квадратные скобки указывают на поиск по индексу, а фигурные скобки служат для поиска в хеше. Передавайте аргументы в функцию с помощью круглых скобок:
$bake_something_ref->( 'cupcakes' );
Также вы можете использовать ссылки на функции как методы объектов (Moose). Это удобно, когда вы уже нашли метод (Рефлексия):
my $clean = $robot_maid->can( 'cleanup' );
$robot_maid->$clean( $kitchen );
Когда вы используете open
(и opendir
) в форме с лексическими дескрипторами файлов, вы имеете дело со ссылками на дескрипторы файлов. Внутренне эти дескрипторы файлов — объекты IO::File
. Вы можете напрямую вызывать их методы. Начиная с Perl 5.14 это можно делать так:
open my $out_fh, '>', 'output_file.txt';
$out_fh->say( 'Have some text!' );
В 5.12 вы должны написать use IO::File;
чтобы включить эту возможность, а в Perl 5.10 и раньше — use IO::Handle;
. Даже более старый код может получать ссылки на тайпглобы:
local *FH;
open FH, "> $file" or die "Can't write '$file': $!";
my $fh = \*FH;
Эта идиома предшествует лексическим дескрипторам файлов (представленным в Perl 5.6.0 в марте 2000 года). Вы всё ещё можете использовать оператор взятия ссылки на тайпглобах для получения ссылок на дескрипторы файлов, глобальные для пакета, такие как STDIN
, STDOUT
, STDERR
или DATA
— но это всё в любом случае глобальные имена.
Использование лексических дескрипторов файлов по возможности предпочтительно. С преимуществами явного задания области видимости, лексические дескрипторы файлов позволяют вам управлять их сроком жизни благодаря возможностям Perl 5 по управлению памятью.
Perl 5 использует технику управления памятью, известную как подсчёт ссылок. Каждое значение в Perl имеет присоединённый к нему счётчик. Perl увеличивает этот счётчик при каждом взятии ссылки на это значение, явном или неявном. Perl уменьшает этот счётчик каждый раз, когда ссылка исчезает. Когда счётчик достигает ноля, Perl может безопасно отправить это значение на переработку.
Как Perl узнаёт, когда можно безопасно освободить память, занимаемую переменной? Как Perl узнаёт, когда безопасно закрыть дескриптор файла, открытый во внутренней области видимости?
say 'file not open';
{
open my $fh, '>', 'inner_scope.txt';
$fh->say( 'file open here' );
}
say 'file closed here';
Во внутреннем блоке в этом примере только один дескриптор $fh
. (Несколько строк в исходном коде упоминают его, но ссылается на него только одна переменная: $fh
.) $fh
существует только в области видимости этого блока. Её значение никогда не покидает блок. Когда выполнение достигает конца блока, Perl перерабатывает переменную $fh
и уменьшает счётчик ссылок содержащегося в ней дескриптора файла. Счётчик дескриптора достигает ноля, так что Perl перерабатывает его для освобождения памяти и неявно вызывает close()
.
Вам не обязательно понимать детали того, как всё это работает. Вам нужно понимать только то, что ваши действия по взятию ссылок и их передаче влияют на то, как Perl управляет памятью (см. Циклические ссылки).
При использовании ссылок как аргументов функции внимательно документируйте ваши намерения. Изменение значений ссылки изнутри функции может стать неожиданностью для вызывающего кода, который не ожидает, что кто-то ещё будет модифицировать его данные. Для изменения содержимого ссылки, не воздействуюя на саму ссылку, скопируйте её значение в новую переменную:
my @new_array = @{ $array_ref };
my %new_hash = %{ $hash_ref };
Это необходимо лишь в немногих случаях, но явное клонирование помогает избежать неприятных сюрпризов для вызывающего кода. Если вы используете вложенные структуры данных или другие сложные ссылки, рассмотрите использование базового модуля Storable
и его функции dclone
(deel cloning, глубокое клонирование).
Агрегатные типы данных Perl — массивы и хеши — позволяют вам хранить скаляры, индексированные по целым числам или строковым ключам. Ссылки в Perl 5 позволяют получать доступ к агрегатным типам данных через специальные скаляры. Вложенные структуры данных в Perl, такие как массив массивов или хеш хешей, становятся возможными благодаря использованию ссылок.
Для объявления вложенной структуры данных используйте синтаксис объявления анонимной ссылки:
my @famous_triplets = (
[qw( eenie miney moe )],
[qw( huey dewey louie )],
[qw( duck duck goose )],
);
my %meals = (
breakfast => { entree => 'eggs',
side => 'hash browns' },
lunch => { entree => 'panini',
side => 'apple' },
dinner => { entree => 'steak',
side => 'avocado salad' },
);
Perl позволяет, хотя и не требует, ставить завершающую запятую для облегчения последующего добавления элементов в список.
Для доступа к элементам вложенных структур данных используйте синтаксис ссылок Perl. Сигил обозначает количество данных, которое должно быть получено, а разыменовывающая стрелка указывает, что значение части структуры данных является ссылкой:
my $last_nephew = $famous_triplets[1]->[2];
my $breaky_side = $meals{breakfast}->{side};
Единственный способ создать многоуровневую структура данных — посредством ссылок, так что использование стрелки излишне. Вы можете опустить её для ясности, за исключением вызовов функций по ссылке:
my $nephew = $famous_triplets[1][2];
my $meal = $meals{breakfast}{side};
$actions{financial}{buy_food}->( $nephew, $meal );
Используйте устраняющий неоднозначность блок для доступа к компонентам вложенной структуры данных, как если бы они были массивами или хешами первого класса:
my $nephew_count = @{ $famous_triplets[1] };
my $dinner_courses = keys %{ $meals{dinner} };
—или для получения среза вложенной структуры данных:
my ($entree, $side) = @{ $meals{breakfast} }
{qw( entree side )};
Пробелы помогают, но не полностью устраняют зашумлённость этой конструкции. Используйте временные переменные для придания ясности:
my $meal_ref = $meals{breakfast};
my ($entree, $side) = @$meal_ref{qw( entree side )};
…или используйте неявное создание псевдонимов в $_
директивой for
, чтобы избежать использования промежуточной ссылки:
my ($entree, $side) = @{ $_ }{qw( entree side )}
for $meals{breakfast};
perldoc perldsc
, кулинарная книга структур данных, даёт обширные примеры того, как использовать разные структуры данных в Perl.
Когда вы пытаетесь записать значение в компонент вложенной структуры данных, Perl по необходимости создаёт путь через структуру данных до пункта назначения:
my @aoaoaoa;
$aoaoaoa[0][0][0][0] = 'nested deeply';
После второй строки кода этот массив массивов массивов массивов содержит ссылку на массив в ссылке на массив в ссылке на массив в ссылке на массив. Каждая ссылка на массив содержит один элемент. Аналогично, обработка неопределённого значения так, как будто это ссылка на хеш во вложенной структуре данных, сделает его таковой:
my %hohoh;
$hohoh{Robot}{Santa} = 'mostly harmful';
Это полезное поведение называется автовивификацией. Оно уменьшает объём инициализирующего кода вложенной структуры данных, но не может определить разницу между честным намерением создать недостающие элементы во вложенной структуре данных и опечаткой. Прагма autovivification
(Прагмы) из CPAN позволит вам отключить автовивификацию в лексической области видимости для определённых типов операций.
Вас может удивить применение преимуществ автовивификации параллельно установке ограничений с помощью strict
. Это вопрос баланса. Будет ли более удобным получить ошибки, изменяющие поведение вашей программы, ценой отключения проверок на ошибки для хорошо инкапсулированных символьных ссылок? Будет ли более удобным позволить структурам данных расти вместо чёткого указания их размера и доступных ключей?
Ответы зависят от вашего проекта. В период начальной разработки позвольте себе свободу экспериментировать. Во время тестирования и развёртывания рассмотрите увеличение строгости для предотвращения нежелательных побочных эффектов. Благодаря лексической области видимости прагм strict
и autovivification
вы можете включить эти варианты поведения там, где это необходимо.
Вы можете проверять свои ожидания перед разыменованием каждого уровня сложной структуры данных, но результирующий код зачастую будет длинным и громоздким. Лучше избегать глубоко вложенных структур данных, пересмотрев свою модель данных в сторону лучшей инкапсуляции.
Сложность разыменовывающего синтаксиса Perl 5, объединённая с потенциальной путаницей нескольких уровней ссылок, может сделать отладку вложенных структур данных сложной. Есть два хороших инструмента визуализации.
Базовый модуль Data::Dumper
преобразует значения произвольной сложности в строки кода Perl 5:
use Data::Dumper;
print Dumper( $my_complex_structure );
Это удобно для определения того, что содержит структура данных, что вы должны получить, и что вы получили вместо этого. Data::Dumper
может выводить объекты, так же как ссылки на функции (если вы установите $Data::Dumper::Deparse
в истинное значение).
Хотя Data::Dumper
— базовый модуль и выводит код на Perl 5, его вывод довольно многословен. Некоторые разработчики предпочитают использовать для отладки модули YAML::XS
или JSON
. Они не выдают код на Perl 5, но их вывод может быть гораздо проще для чтения и понимания.
Имеющаяся в Perl 5 система управления памятью с помощью подсчёта ссылок (Счётчики ссылок) имеет один недостаток, заметный для пользовательского кода. Две ссылки, которые в конечном счёте указывают друг на друга, образуют циклическую ссылку, которую Perl не может уничтожить сам. Рассмотрим биологическую модель, где каждая сущность имеет двух родителей и ноль или больше детей:
my $alice = { mother => '', father => '' };
my $robert = { mother => '', father => '' };
my $cianne = { mother => $alice, father => $robert };
push @{ $alice->{children} }, $cianne;
push @{ $robert->{children} }, $cianne;
И $alice
, и $robert
содержат ссылку на массив, которая содержит $cianne
. Поскольку $cianne
— ссылка на хеш, которая содержит $alice
и $robert
, Perl никогда не сможет уменьшить счётчик ссылок любого из этих трёх человек до нуля. Он не распознает, что эти циклические ссылки существуют, и не сможет управлять сроком жизни этих сущностей.
Или разбейте счётчик ссылок сами вручную (очистив детей $alice
и $robert
или родителей $cianne
), или используйте слабые ссылки. Слабая ссылка — это ссылка, которая не увеличивает счётчик ссылок значения, на которое ссылается. Слабые ссылки доступны через базовый модуль Scalar::Util
. Его функция weaken()
предотвращает увеличение счётчика ссылок:
use Scalar::Util 'weaken';
my $alice = { mother => '', father => '' };
my $robert = { mother => '', father => '' };
my $cianne = { mother => $alice, father => $robert };
push @{ $alice->{children} }, $cianne;
push @{ $robert->{children} }, $cianne;
weaken( $cianne->{mother} );
weaken( $cianne->{father} );
Теперь $cianne
сохранит ссылки на $alice
и $robert
, но эти ссылки сами по себе не будут удерживать сборщик мусора Perl от разрушения этих структур данных. Большинство структур данных не требуют слабых ссылок, но когда они необходимы — они бесценны.
Хотя Perl способен обрабатывать структуры данных, вложенные так глубоко, как вы только можете представить, человеческая стоимость понимания этих структур данных и их взаимоотношений — не говоря о сложном синтаксисе — высока. После двух или трёх уровней вложенности, задумайтесь, не позволит ли вам моделирование разных компонентов вашей системы как классов и объектов (Moose) сделать код чище.