Логотип StingRay

Социальные сети
FacebookInstagramRSSTwitterYouTubeВ контактеОдноклассники
FacebookInstagramRSSTwitterYouTubeВ контактеОдноклассники
Силуэт человека

Критический анализ языка PERL

Аннотация
Введение
1. Лексемы
 1.1. Комментарии
 1.2. Идентификаторы
 1.3. Числа
 1.4. Строки
2. Типы данных, переменные и константы
 2.1. Скаляры
 2.2. Массивы
 2.3. Хеши
 2.4. Переменные и константы
3. Операции и выражения
 3.1. Арифметические операции
 3.2. Операции сравнения
 3.3. Прочие операции и особенности выражений
4. Операторы
 4.1. Операторы присваивания
 4.2. Операторы ветвления
 4.3. Операторы цикла
 4.4. Операторы безусловного перехода
5. Подпрограммы
 5.1. Синтаксис определения и вызова подпрограмм
 5.2. Передача параметров и возврат значений
 5.3. Область видимости идентификаторов
6. Модульное программирование
 6.1. Синтаксис определения модуля
 6.2. Экспорт и импорт
7. Объектно-ориентированное программирование
 7.1. Синтаксис определения класса, область видимости методов и свойств
 7.2. Вызов методов
 7.3. Создание и удаление объектов
 7.4. Наследование и полиморфизм
Заключение
Литература

Аннотация

Статья посвящена популярному языку программирования PERL. В данной статье представлен детальный анализ и критика основных языковых средств PERL. Подробно рассмотрены все элементы синтаксиса, начиная с лексем и заканчивая средствами поддержки ООП. Особое внимание уделено понятию типа данных и механизму типизации, а также базовым понятиям и концепциям, лежащим в основе модульного и объектно-ориентированного программирования.

Осознание изложенных в статье идей не предполагает знакомства с языком PERL, однако необходимо знание какого-либо языка и понимание основных понятий, лежащих в основе программирования, например, подпрограмма, модуль, класс, тип данных и проч.

Статья предназначена для всех, интересующихся программированием вообще и языком PERL в частности. Автор надеется, что данная статья будет интересна и полезна как начинающим, так и опытным программистам.

Введение

Данная статья посвящена критике языка PERL и является выражением субъективных взглядов автора на данный язык, сформировавшихся в результате его практического применения в сфере разработки Internet-приложений для e-commerce. Предметом данной статьи является, прежде всего, сам язык PERL, то есть, главным образом, его конструкции ("языковые средства"), а также особенности их использования. При этом встроенные переменные и функции рассматриваются лишь отчасти и только в контексте тех или иных конструкций. Приёмы или стиль программирования на языке PERL, равно как и возможности его использования для решения тех или иных конкретных классов задач не рассматриваются вовсе и не являются предметом настоящей статьи. Статья построена по схеме "от простого – к сложному", то есть последовательно рассматриваются все основные элементы языка, начиная от лексем и заканчивая средствами поддержки ООП.

Web-программированием сегодня не занимается разве что ленивый, так что, предвидя нападки со стороны "корифеев" от Apache+CGI типа "а зачем PERL – ведь есть php!", автор желает заявить следующее. Здесь язык PERL намеренно рассматривается в отрыве от тех областей, в которых он получил наиболее широкое применение (автоматизация административных задач в системах типа Unix и Web-программирование). Это обусловлено сильным развитием как самого языка, так и его "окружения" (обилие разного рода библиотек, модулей, классов, а также вспомогательных инструментов), что позволяет рассматривать PERL как, фактически, универсальную среду программирования, практически пригодную для решения очень разных задач, включая не только серверные приложения, но и приложения, ориентированные на конечного пользователя (средства поддержки столь модных нынче графических интерфейсов также имеются).

И ещё. Автор вовсе не считает себя "гуру" от программирования, однако вполне определённый опыт, полученный при выполнении конкретных проектов, а также разносторонние познания в этой области (заметим лишь, что количество языков программирования, которые освоены автором на практике, переваливает за десяток) дают автору основание и моральное право высказывать свои критические замечания, которые отнюдь не являются следствием эмоций или поверхностных представлений, а суть обоснованные суждения, в чём читателю и предлагается убедиться. Везде, где это уместно, автор проводит аналогии с некоторыми из известных ему языков программирования.

Автору хотелось бы сделать ещё одно замечание касательно терминологии. По сути, все известные автору реализации языка PERL, включая стандартную, поставляемую в составе Unix-систем, являются интерпретаторами. Однако очевидно, что PERL не является строго построчным интерпретатором, поскольку он выполняет синтаксический анализ всей программы, включая внешние модули, загруженные с помощью предложения use, ещё до начала её выполнения, при этом, по-видимому, создаётся некоторое внутреннее представление по крайней мере какой-то части программы. Правда, здесь есть некоторые исключения, вроде блоков BEGIN, которые выполняются сразу же после их анализа, даже если анализ всей программы ещё не завершён, или модулей, подключаемых с помощью предложения require, анализ которых выполняется только после выполнения самого предложения require, но эти исключения, по большому счёту, не изменяют положения дел (тем более что, скажем, предложение require использовать не рекомендуется), поскольку запуск программы на выполнение может быть чётко разделён на две фазы: синтаксический анализ и собственно выполнение (интерпретация). В литературе по PERL почему-то принято фазу синтаксического анализа называть этапом компиляции, что, вообще говоря, не совсем верно, однако автор не стал ломать устоявшуюся традицию, так что при обсуждении вопросов, относящихся к фазе синтаксического анализа, автор будет применять термин "этап компиляции".

Соответственно, при обсуждении вопросов, относящихся к моменту собственно выполнения программы, автор будет употреблять термин "этап выполнения". Если необходимо особо подчеркнуть, что те или иные результаты могут быть получены только на этапе выполнения, и причём, возможно, для получения этих результатов может потребоваться провести специально спланированные испытания, то автор будет употреблять словосочетание "на этапе выполнения при отладке" (некоторые почему-то считают, что отладка – это приведение программы к "компилируемому состоянию", что является величайшим заблуждением вообще, а в случае PERL, как будет показано в дальнейшем, особенно).

В условиях отсутствия формального определения языка PERL, при написании статьи автор использовал, помимо собственного опыта, документацию, которая входит в "комплект поставки" PERL (главным образом man-страницы), как наиболее авторитетный источник. Кроме того, полезной оказалась книга [1], написанная Larry Wall (автором данного языка), под названием "Programming PERL", вышедшая в издательстве O'Reilly & Associates, которая позволила познакомиться со взглядами автора языка PERL на жизнь вообще и на программирование в частности. При изложении тех или иных фактов, почерпнутых из документации, автор старался давать ссылки на конкретные man-страницы. Все цитаты, приведённые в данной статье, снабжены ссылками на соответствующие источники, список которых приведён к конце статьи. Цитаты из англоязычных источников снабжены переводами.

Всё, о чём говорится в данной статье, относится к языку PERL версии 5.005. Все упомянутые в статье факты, относящиеся к языку PERL, и все приведённые в тексте примеры проверены как на платформе Unix (Red Hat Linux 6.02), так и на платформе Win32 (Microsoft Windows 95 OSR 2). Все примеры снабжены комментариями, поясняющими ту или иную мысль, которую эти примеры призваны проиллюстрировать. Факты, относящиеся к другим языкам программирования, также проверены автором лично.

1. Лексемы

Анализ языка PERL начнём с исследования его лексем. В данном разделе рассмотрены не все классы лексем языка PERL, а только те, чьи особенности реализации представляют интерес с точки зрения критики.

1.1. Комментарии

В PERL предусмотрено два типа комментариев: однострочный и документирующий. Многострочный комментарий отсутствует, а ведь "комментарии в общем будут лучшими, когда они помещаются в многострочных блоках, которые чередуются с блоками кода программы. Для этого комментарий должен на высоком уровне описывать, что делают несколько последующих строк кода. Если комментарии попадаются через строчку, то это похоже на чтение двух книг одновременно, причём по строке из каждой поочерёдно" [7]. Не случайно, наверное, в языках Pascal и C многострочный комментарий является единственным поддерживаемым на уровне языка средством документирования программ.

В качестве многострочного комментария в PERL предлагается использовать документирующий, однако это не всегда удобно, поскольку любой таким образом оформленный многострочный комментарий будет включён в выходной файл, генерируемый утилитой perldoc, как часть документации модуля. Кроме того, pod-директивы, используемые при оформлении документирующего комментария, должны начинаться в первой позиции строки (символом "="), что также не особенно удобно при обычном многострочном комментировании.

Однако можно применить один трюк, который позволяет включать многострочные комментарии так, чтобы они не попали в выходную документацию. Он состоит в том, чтобы вместо стандартных pod-директив использовать незнакомую perldoc директиву, в результате данная директива не будет интерпретироваться PERL, поскольку в первой позиции строки стоит символ "=", но и perldoc она будет молча пропущена. Кстати, этот трюк описан в man-страницах (именно perlsyn), поставляемых с PERL.

Кстати, поддержка документирующего комментария как отдельного синтаксического средства представляется избыточным – при должной реализации соответствующей утилиты выделения документации из исходного модуля (в случае PERL это perldoc) можно было бы обойтись многострочным комментарием с включением в него специальных тегов, распознаваемых этой утилитой.

У любого здорового программиста в связи с этим возникает один вопрос: а что мешало сделать так, как сделано, скажем, в Java? Это вопрос чисто риторический – трюкачество без меры есть стиль программирования, навязываемый языком PERL.

1.2. Идентификаторы

Правила построения идентификаторов в PERL, мягко говоря, причудливы. Кроме стандартного набора букв, цифр и символа "_" в пользовательских идентификаторах допускается использование символа "'", причём в качестве первого тоже. Зачем и кому это понадобилось – непонятно, ведь данная особенность приводит к появлению ещё одного (среди бесчисленного множества других) потенциального источника ошибок, которые, правда, можно обнаружить на этапе компиляции. Так, следующий код компилируется исправно:

$'Foo = "john";
print($'Foo);

А этот приведёт к ошибке компиляции:

$Foo' = "john";
print($Foo');

На это можно возразить, что, мол, если не нужно – не используйте. Однако здесь есть ещё одна тонкость, которая связана с модульностью. Дело в том, что в ранних версиях PERL в качестве разделителя элементов квалифицированного идентификатора использовался именно символ "'", причём в целях обратной совместимости это поддерживается до сих пор. А это может привести к ошибке, которую будет очень трудно найти. Например, следующий код будет компилироваться и выполняться без ошибок, но результат, скорее всего, будет отличным от ожидаемого:

$Owner = "John";
print("That's $Owner's house.");

Если только у вас на самом деле нет модуля Owner, который экспортирует переменную s, то на терминале будет напечатано:

That's  house.

Эта тонкость (чтобы не назвать глупостью), к счастью, описана в man-страницах (именно perlmod), откуда и позаимствован приведённый пример. Теперь, кстати, в качестве символа-разделителя в квалифицированных идентификаторах используется составной символ "::". Наверное, ребята ну очень хотели сделать всё как в C++ (о чём прямо заявлено в той же perlmod). Но что же предлагается использовать для решения этой проблемы? А вот что:

print("That's ${Owner}'s house."); # блок или элемент хеша?

Ничего, кроме горькой усмешки, это вызывать не может, тем более что с точки зрения здорового программирования гораздо надёжнее вместо автоматических подстановок использовать обычную операцию конкатенации.

1.3. Числа

И здесь не обошлось без сюрпризов. Кроме стандартных форм записи целых и вещественных чисел вроде

123     # целое
123.123 # вещественное
.5      # при записи десятичных дробей ноль в целой части можно опускать
.5E-10  # экспоненциальная форма
0xABCD  # шестнадцатеричные числа записываются как в C
0377    # если первая цифра – ноль, то это восьмеричное число

PERL поддерживает и такую:

9_123_456 # это, видимо, для "удобства чтения"

и даже, как показывает практика, такую:

9_1__2_3___456_____ # что сие означает, надо спросить у Larry Wall

Но только будьте осторожны! Ни в коем случае не ставьте символ "_" в начале числа – будут сюрпризы, например:

$f = _1;
print($f + 2); # будет напечатано 2 вместо ожидаемых 3
print(_1 + 2); # а так вообще ничего не будет напечатано, здорово, правда?

PERL скорее сделает невозможное, чем выдаст сообщение об ошибке. Ваша программа заработает сразу же и будет работать всегда, не вызывая сообщений об ошибках, только насколько правильно – это, как говорится, "тонкий философский вопрос".

1.4. Строки

Строки обычно считаются обычными лексемами, но в PERL – это операции. Дело в том, что существует несколько способов обозначения строк, в зависимости от используемого способа PERL выполняет те или иные преобразования над их содержимым на этапе выполнения. Причём для каждого способа существует две формы: сокращённая и полная. При использовании полной формы программисту предоставлена возможность самостоятельно выбрать символы, которые будут использованы как символы-ограничители строки, причём если в качестве символов-ограничителей выбраны скобки (круглые, прямоугольные, угловые или фигурные), то они обязательно должны быть парными, в остальных случаях в качестве начального и концевого ограничителя должен использоваться один и тот же символ. В приведённой ниже таблице (она позаимствована из man-страницы perlop) указаны обе формы для каждого из способов обозначения строк и их назначение, при описании полной формы для определённости в качестве символов-ограничителей использованы фигурные скобки.

Сокращённая форма Полная форма Выполнение подстановок Назначение
'' q{} нет строковый литерал
"" qq{} да строковый литерал
`` qx{} да, если символ-ограничитель не "'" команда системного интерпретатора команд
qw{} нет массив строк, содержащих слова исходной строки
// m{} да сопоставление с шаблоном
qr{} да предварительная компиляция шаблона
s{}{} да замена подстроки по шаблону
tr{}{} нет замена/удаление символов

Если использованный способ обозначения строки допускает выполнение подстановки, то перед преобразованием PERL, рассматривая подстроки, следующие после символов "$" и "@", встречающихся в строке, как идентификаторы соответствующих скаляров и массивов, вставляет их значения, которые автоматически преобразуются к строковому типу, в строку, как если бы они были указаны непосредственно, причём значения элементов массива сливаются в одну сплошную подстроку. Кроме того, распознаются около 10 esc-последовательностей, начинающихся с символа "\" ("\n", "\t" и проч.).

Автоматические подстановки значений переменных в строки, а также разнообразные возможности сопоставления, выделения и замены подстрок по шаблону традиционно считаются сильной стороной языка PERL и одним из главных аргументов, используемых апологетами данного языка. Тем не менее, при ближайшем рассмотрении оказывается, что наличие данных возможностей скорее служит признаком слабости, нежели силы. Необходимость в автоматических подстановках, которые были взяты на вооружение, по всей видимости, у Bourne Shell, более чем сомнительна и, по сути, является опасной, учитывая трудности с идентификаторами, обсуждавшимися ранее. Гораздо надёжнее и проще с точки зрения чтения программ использовать операцию конкатенации строк, а не пытаться искать приключения.

Шаблоны (регулярные выражения) заслуживают отдельного обсуждения. Однако наличие встроенной непосредственно в язык поддержки такого рода возможностей, как сопоставление с шаблоном, является признаком проблемной ориентированности языка, что неприемлемо для языков общего назначения. В этом наиболее сильно проявляется тот факт, что изначально область применения языка PERL была вполне определённой и далеко не такой широкой, какой она является сейчас. Да, шаблоны полезны и позволяют решать достаточно широкий класс задач, связанных с анализом и обработкой строк, но включение их поддержки непосредственно в язык заметно усложняет его, для языка общего назначения логично было бы вынести поддержку шаблонов во внешние библиотеки. Однако в случае PERL это было бы неприемлемо с точки зрения производительности, так как, если исключить шаблоны, в нём не останется средств эффективной реализации операций обработки строк, поскольку PERL, являясь языком, реализующим концепцию автоматической сборки мусора, не поддерживает указателей и адресной арифметики в явном виде, как это делает, скажем, язык C.

Кроме того, для решения подавляющего большинства рядовых задач обработки строк, возникающих при разработке приложений, не связанных непосредственно с синтаксическим анализом, вполне достаточно простых функций, наподобие тех, что реализованы в стандартной библиотеке string языка C, тем более что их использование в подобных случаях гораздо более эффективно. Однако многочисленные руководства типа "секреты и советы" по программированию на PERL, несмотря на поддержку достаточного набора простых функций обработки строк в нём, пропагандируют использование выражений с шаблонами везде, где только возможно, даже там, где их применение ничем не оправдано и только затрудняет понимание текста программы. Так, например, для выделения TLD (top level domain) из полного доменного имени вполне можно использовать простые функции обработки строк rindex и substr, как показано в следующем фрагменте исходного кода:

$TLD = "";
$p = rindex($DomainName, ".");

# Если символ "." не найден, то доменное имя не верно,
# и TLD не может быть выделен, в противном случае выделяем TLD функцией substr
if ($p != -1) {
   $TLD = substr($DomainName, $p + 1);
}

Однако в мире программистов на PERL такой простой, ясный и эффективный подход, по-видимому, не приветствуется, поскольку нужно слишком много писать и слишком много думать. Ещё бы! Ведь если верить словам автора PERL, то "… the three great virtues of a programmer: laziness, impatience, and hubris"1 [1]. С их точки зрения, гораздо удобнее написать всё, по возможности, в одну строку, например так:

($TLD) = $DomainName =~ m/.*\.(.*)/; # $TLD необходимо заключить в круглые
                                     # скобки, указав тем списковый контекст

Однако несмотря на то, что в первом варианте в пять раз больше строк исходного кода, чем во втором, последний менее эффективен, что легко установить, выполнив приведённые примеры в цикле с контролем времени выполнения при помощи функции time. Автор неоднократно повторил данный эксперимент на компьютере с поцессором Intel Pentium 100 MHz и 32 Mb оперативной памяти, причём в одной версии шаблон, по которому велось выделение во втором фрагменте, был предварительно откомпилирован вне тела цикла при помощи qr, а в другой – этот шаблон был указан непосредственно в выражении вместе с опцией o (compile only once). Длина строки $DomainName составляла 78000 символов. Первый фрагмент в цикле из 10000 итераций потребовал для выполнения всего 1-2 секунды, а второй (в обеих версиях) – 167-168 секунд. Таким образом, пропаганда повсеместного использования операций с шаблонами фактически стимулирует применение их не по назначению, в то время как можно было бы обойтись более простыми и более эффективными средствами.

Наконец, многочисленные выражения с шаблонами могут весьма затруднить понимание программы, что становится особенно критичным при отладке или доказательстве правильности программ. В связи с этим уместно привести слова Н. Вирта: "Богатый по своим возможностям язык программирования может приветствоваться профессиональным "программистом-писателем", получающим удовольствие от процесса освоения всех его сложных особенностей. Однако интересы "программиста-читателя" требуют от языка программирования разумной скромности в предоставляемых им возможностях. Надо сказать, что к категории "программистов-читателей" могут быть отнесены не только люди (в том числе и сам автор программы), но и трансляторы с различных языков программирования и средства автоматического доказательства правильности программ" [5].

2. Типы данных, переменные и константы

Язык PERL является нетипизированным языком. В нём начисто отсутствуют базовые типы и механизм статической типизации. Это означает, что в PERL не существует таких понятий, как числовой тип, символьный тип, логический тип, строковый тип, как не существует и возможности обеспечить контроль типов при вычислении выражений и присваиваниях ни на этапе компиляции, ни на этапе выполнения. Значения любых, по сути разных, типов при необходимости преобразуются PERL автоматически на этапе выполнения, так что PERL, с точки зрения программиста, совершенно не в состоянии, например, отличить числовое значение от строкового или указателя, а строковое значение – от логического. Автоматические преобразования типов PERL выполняет исходя из контекста, в котором используется та или иная переменная. Контекст определяется, во-первых, операцией, которая должна быть выполнена со значением переменной, во-вторых, оператором, содержащим выражение, в вычислении которого участвует значение переменной, а в-третьих, также специальным символом, указанным в качестве первого символа идентификатора переменной (будем называть его символом контекста, хотя одного этого символа недостаточно для определения результата преобразования типов). Символ контекста является, фактически, неотъемлемой частью идентификатора и указывает тип структуры переменной. Существует 3 типа структур и соответствующих им символов контекста: скаляр (символ "$"), массив (символ "@") и хеш (символ "%").

Поскольку символ контекста является неотъемлемой частью идентификатора переменной, то переменные, скажем, $a, @a и %a – это совершенно разные переменные, каждой их которых соответствует своя область памяти. Однако, если вы забыли указать символ контекста перед идентификатором переменной, то, если эта переменная используется в выражении, не получите от PERL сообщения об ошибке ни на этапе компиляции, ни на этапе выполнения, а ваша программа, скорее всего, будет работать неправильно, например:

$i = 1;
$s = "Number ".i;
print($s); # печать строки "Number i" вместо ожидаемой "Number 1"

Поиск и исправление такой ситуации может потребовать очень и очень больших усилий, так что за контекстом приходится постоянно следить самому. Правда, при попытке выполнить присваивание переменной без символа контекста будет выдана ошибка компиляции, например:

$i = 1;
print(i++); # ошибка компиляции,
            # операция инкремента неявно изменяет значение переменной

И вообще, в виду отсутствия статических типов в языке PERL, у программиста нет возможности явно описывать типы и чётко отделять описания типов от описаний переменных, а описания переменных – от операций над переменными. А ведь "средства описания типов способствуют не только улучшению прозрачности и повышению надёжности программ, но и генерации… более эффективного объектного кода" [5].

2.1. Скаляры

Ввиду того, что PERL является нетипизированным языком, скаляр может принимать значения различных типов. Фактически, в качестве значения скаляра может выступать число (целое либо вещественное) либо строка, либо ссылка (адрес) на другую переменную либо функцию. Важным их свойством является то, что скаляры атомарны, то есть содержат одно и только одно значение и не имеют внутренней структуры.

В качестве значения скаляра может выступать и специальное значение undef, аналог null в SQL. В отличие от SQL, где null = null есть false, в PERL undef == undef есть true. В связи с этим непонятно, зачем ещё нужна встроенная функция defined, которая возвращает true, если её аргумент не равен undef, и false в противном случае.

В PERL отсутствуют символьный и логический типы. Если с отсутствием первого ещё можно смириться, то отсутствие второго не вызывает ничего, кроме раздражения. Исходя из руководств, в качестве логического значения true принимается любая непустая строка либо число, отличное от нуля. При этом строка "0", которая в числовом контексте эквивалентна числу 0, считается логическим false, хотя пустой не является. Более того, строка типа "000", содержащая более одного символа "0", которая в числовом контексте также эквивалентна числу 0, уже является логическим true. Но и это ещё не всё. Число 0, записанное в экспоненциальной форме как 0E0, хоть и является стопроцентным числом 0, в логическом контексте считается true. Кроме того, как оказалось, в качестве логического true воспринимается ссылка, не равная undef. И вообще, значение undef всегда считается логическим false. Таким образом, в плане поддержки логического типа язык PERL много более извращён, чем язык C. Непонятно, Larry Wall сэкономить что ли на этом решил?

Но и это ещё куда ни шло по сравнению с тем обстоятельством, что в PERL вообще невозможно различать строки и числа. Дело в том, что PERL всегда представляет числа как строки, а неявные преобразования происходят по мере необходимости при вычислениях выражений и присваиваниях. Возможно, что с точки зрения автора данного языка это "круто", да вот с точки зрения практического программирования это совсем не круто, поскольку чревато такими трудноуловимыми "глюками" (иначе и не назовёшь), о каких Larry Wall, наверное, и не догадывался. Вот реальный пример из собственной практики автора.

Итак, есть модуль, реализующий набор функций для обработки дат. Функции этого модуля, манипулирующие с датами, принимают значения дат в виде упорядоченных троек чисел, обозначающих, соответственно, год, месяц и день. Кроме того, модуль реализует пару функций, одна из которых для переданного строкового представления даты в виде "YYYY-MM-DD" (а если более точно, то в виде ^\d{1, 4}-\d{1, 2}-\d{1, 2}$; такая форма представления дат используется, например, сервером БД MySQL) возвращает соответствующую упорядоченную тройку чисел, которую затем можно использовать с функциями обработки дат, а вторая выполняет обратное преобразование, формируя из переданной упорядоченной тройки чисел строковое представление даты точно в виде "YYYY-MM-DD", которое затем можно использовать, например, при конструировании SQL-запроса. Вот их текст:

 0: sub ToString {
 1:    my ($Year, $Month, $Day) = @_;
 2:
 3:    return
 4:       (
 5:          $Year < 10 ? "000" : (
 6:             $Year < 100 ? "00" : ($Year < 1000 ? "0" : "")
 7:          )
 8:       ).$Year."-".
 9:       ($Month < 10 ? "0" : "").$Month."-".
10:       ($Day < 10 ? "0" : "").$Day;
11: }
12:
13: sub ToNumberList {
14:    my $Date = $_[0];
15:    my ($Year, $Month, $Day) =
16:       $Date =~ /^(\d{1, 4})-(\d{1, 2})-(\d{1, 2})$/;
17:
18:    if (!(defined($Year) && defined($Month) && defined($Day))) {
19:       ($Year, $Month, $Day) = (0, 0, 0);
20:    }
21:
22:    return ($Year, $Month, $Day);
23: }

Проблема была в том, что при попытке выполнить вот такой вызов с совершенно правильным аргументом

ToString(ToNumberList("0800-01-01"));

возвращалась неправильная строка "00800-001-001", которая потом приводила к ошибке там, где она использовалась далее. Причина на самом деле в том, что PERL хранит числа в виде строк, а при вычислениях автоматически преобразует строки в числа и наоборот. Поэтому для того, чтобы решить данную проблему и сделать модуль "пуленепробиваемым", пришлось строку 1 заменить на следующие:

my $Year = $_[0] + 0;
my $Month = $_[1] + 0;
my $Day = $_[2] + 0;

а строку 22 на вот это:

return ($Year + 0, $Month + 0, $Day + 0);

что хоть и выглядит очень глупо, зато работает справно.

2.2. Массивы

Массивы в PERL рассматриваются как упорядоченные линейные множества скаляров. Таким образом, во-первых, на уровне языка поддерживаются только одномерные массивы, а во-вторых – элементы массива могут быть, фактически, произвольного типа. Несмотря на то, что в PERL существует возможность блочного конструирования массивов с использованием уже существующих массивов, например так:

@Array = (1, 2, 3);
@NewArray = (@Array, 4, @Array); # это эквивалентно такому определению:
                                 # @NewArray = (1, 2, 3, 4, 1, 2, 3);

в сконструированном таким образом массиве исходные массивы неразличимы, как если бы их элементы были непосредственно перечислены при описании массива. Для работы с многомерными структурами данных приходится вручную описывать массивы ссылок на массивы нужных значений, например так, как это описано в man-странице perllol:

@LoL = (
   ["fred", "barney"],
   ["george", "jane", "elroy"],
   ["homer", "marge", "bart"]
);

и при этом иметь в виду контекст при получении доступа к элементам такой структуры:

print(${LoL[1]}->[1]); # печать строки "jane"
print($LoL[1][1]);     # как это ни странно, так тоже работает

При увеличении размерности, скажем, до трёх, общий подход сохраняется, и придётся определить массив ссылок на массивы ссылок на массивы значений. Разыменовывать ссылки при этом не обязательно, так же как и в двумерном случае.

Массивы индексируются числами (или строками, представляющими числа), начиная с нуля, то есть первый элемент имеет индекс 0, второй – 1 и т. д. Очень интересным является то, что индексы могут быть, во-первых, отрицательными, а во-вторых – даже вещественными (это же PERL!). В случае отрицательных индексов отсчёт ведётся с конца массива, то есть последний элемент имеет индекс -1, предпоследний – -2 и т. д. При индексировании вещественными числами (как положительными, так и отрицательными) в качестве индекса используется целая часть числа. Кстати, по какой-то непонятной причине возникает ошибка времени выполнения при индексации массивов вещественными числами, представленными в экспоненциальной форме (с чего бы это, ведь это же так естественно!). Но и здесь можно исхитриться, заключив записанное в экпоненциальной форме число в кавычки, вот так:

@Array = ("x", "y", "z");
print($Array["0E0"]); # печать первого элемента массива @Array

Чудеса, да и только.

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

@Array = ("x", "y", "z");
$Array[5] = "a"; # теперь @Array содержит следующее:
                 # ("x", "y", "z", undef, undef, "a")

Такая динамичность массивов исключает возможность