В этой части Учебника Perl мы рассмотрим структуры данных, существующие в Perl и узнаем, как их использовать.

В Perl 5 есть 3 основных структуры данных. Это скаляры, массивы и хэши. В других языках последние также могут называться словарями, справочными таблицами или ассоциативными массивами.

Перед переменными в Perl всегда ставится знак, называемый "сигил". Этими знаками являются $ для скаляров, @ для массивов, и % для хэшей.

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

Название скалярной переменной всегда начинается с $ (знак доллара), за которым следуют буквы, числа и подстрочия. В качестве названия переменной может использоваться $name или $long_and_descriptive_name. Также встречается вариант $LongAndDescriptiveName, который обычно называют "CamelCase", но в Perl-сообществе обычно предпочитают названия, состоящие только из символов нижнего регистра, с подстрочиями, отделяющими слова.

Поскольку мы всегда используем strict, нам всегда нужно объявлять переменные с помощью my. (Позже вы также узнаете об our и некоторых других способах, но пока что будем использовать только объявление через my.) Мы можем либо присвоить переменной значение сразу, как в этом примере:

use strict;
use warnings;
use 5.010;

my $name = "Foo";
say $name;

либо сначала объявить ее, а присвоить значение позже:

use strict;
use warnings;
use 5.010;

my $name;

$name = "Foo";
say $name;

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

Если мы объявили переменную, но еще не присвоили ей значение, она содержит значение, называемое undef, которое схоже с NULL в базах данных, но имеет немного отличающееся поведение.

Можно проверить, является ли переменная undef с помощью функции defined:

use strict;
use warnings;
use 5.010;

my $name;

if (defined $name) {
  say 'defined';
} else {
  say 'NOT defined';
}

$name = "Foo";

if (defined $name) {
  say 'defined';
} else {
  say 'NOT defined';
}

say $name;

Можно присвоить для скалярной переменной значение undef:

$name = undef;

Скалярные переменные могут содержать числа или строки. То есть я могу написать:

use strict;
use warnings;
use 5.010;

my $x = "hi";
say $x;

$x = 42;
say $x;

и это сработает.

Как это работает вместе с операторами и перегрузкой операторов в Perl?

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

Так что, если у меня есть две переменные, содержащие числа, оператор в итоге решает, будут ли они восприниматься как числа или как строки:

use strict;
use warnings;
use 5.010;

my $z = 2;
say $z;             # 2
my $y = 4;
say $y;             # 4

say $z + $y;        # 6
say $z . $y;        # 24
say $z x $y;        # 2222

+, оператор числового сложения, прибавляет два числа, так что и $y и $z ведут себя как числа.

., оператор конкатенации строк, так что $y и $z ведут себя как строки. (В других языках это бы могло называться строковым сложением.)

x, оператор повторения, повторяет строку слева количества раз, определяемое числом справа, так что в этом случае $z действует как строка, а $y - как число.

Результаты были бы теми же, если бы обе переменные создавались как строки:

use strict;
use warnings;
use 5.010;

my $z = "2";
say $z;             # 2
my $y = "4";
say $y;             # 4

say $z + $y;        # 6
say $z . $y;        # 24
say $z x $y;        # 2222

И даже если одна из них создана как число, а вторая как строка:

use strict;
use warnings;
use 5.010;

my $z = 7;
say $z;             # 7
my $y = "4";
say $y;             # 4

say $z + $y;        # 11
say $z . $y;        # 74
say $z x $y;        # 7777

Perl автоматически конвертирует числа в строки и наоборот, как того требует оператор.

Мы называем это числовым и строковым контекстом.

Вышеупомянутые случаи довольно просты. Преобразование числа в строку - по сути просто заключение его в кавычки. Преобразование строки в числа, когда она состоит только из цифр - простой случай. То же будет, если в ней присутствует десятичная точка, например "3.14". Вопрос в том, что если в строке содержатся символы, не образующие число? Например "3.14 is pi". Как тогда она себя поведет в числовых операциях (то есть в числовом контексте)?

Это тоже несложно, хотя может потребовать некоторого объяснения.

use strict;
use warnings;
use 5.010;

my $z = 2;
say $z;             # 2
my $y = "3.14 is pi";
say $y;             # 3.14 is pi

say $z + $y;        # 5.14
say $z . $y;        # 23.14 is pi
say $z x $y;        # 222

Когда строка попадает в числовой контекст, Perl смотрит на левую часть строки, и пытается превратить ее в число. Пока это получается, эта часть становится числовым значением переменной. В числовом контексте (+) строка "3.14 is pi" считается числом 3.14.

В каком-то смысле это совершеннейший прозвол, но так это работает, так что мы живем с этим.

Код выше также выдаст предупреждение в стандартный канал ошибок (STDERR):

Argument "3.14 is pi" isn't numeric in addition(+) at example.pl line 10.

в случае, если вы использовали use warnings, что настоятельно рекомендуется. Использование предупреждений поможет заметить, если что-то пойдет не совсем так, как предполагалось. Надеюсь, результат выполнения $x + $y теперь вполне ясен.

За кадром

На всякий случай, perl не конвертирует $y в 3.14, он просто использует числовое значение для сложения. Думаю, это объясняет и результат $z . $y. В этом случае perl использует исходное строковое значение.

Возможно, вы удивлены, что $z x $y выдает 222, хотя справа у нас 3.14. Дело в том, что perl может повторить строку только целое число раз... В этой операции perl молча округляет число справа. (Если задуматься, можно понять, что "числовой" контекст, о котором шла речь раньше, на самом деле включает в себя несколько подконтекстов, один из которых - "целочисленный". В большинстве случаев perl делает то, что подсказывает здравый смысл большинству людей-непрограммисов)

Кроме того, мы даже не видим предупреждения "partial string to number conversion", как в случае с +.

Это не из-за другого оператора. Если закомментировать сложение, мы увидим это предупреждение уже на этой операции. Причина отсутствия второго предупреждения в том, что когда perl создал числовое значение строки "3.14 is pi", он сохранил его в потайном кармане переменной $y. Так что по сути $y с тех пор имеет и строковое, и числовое значение, и нужное будет использоваться в соответствующие моменты уже без преобразования.

Есть еще три вещи, которые я хотел бы упомянуть. Первая это поведение переменной со значением undef, вторая - fatal warnings, и третья - как избежать автоматического перевода строк в числа.

undef

Если у меня в переменной содержится undef, что для большинства значит "ничего", ее все равно можно использовать. В числовом контексте она будет 0, а в строковом - пустой строкой:

use strict;
use warnings;
use 5.010;

my $z = 3;
say $z;        # 3
my $y;

say $z + $y;   # 3
say $z . $y;   # 3

if (defined $y) {
  say "defined";
} else {
  say "NOT";          # NOT
}

С двумя предупреждениями:

Use of uninitialized value $y in addition (+) at example.pl line 9.

Use of uninitialized value $y in concatenation (.) or string at example.pl line 10.

Как можно видеть, в конце скриппта переменная все еще undef, так что условное выражение выдаст "NOT".

Fatal warnings

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

use warnings FATAL => "all";

С этим скрипт выведет число 3, а затем выбросит исключение:

Use of uninitialized value $y in addition (+) at example.pl line 9.

Это то же сообщение, которое раньше было предупреждением, но теперь после этого выполнение скрипта останавливается. (Если, конечно, исключение не было поймано, но об этом мы поговорим в другой раз.)

Избежание автоматического перевода строки в число

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

Для этого нам нужно подгрузить модуль Scalar::Util и содержащуюся в нем подпрограмму looks_like_number.

use strict;
use warnings FATAL => "all";
use 5.010;

use Scalar::Util qw(looks_like_number);

my $z = 3;
say $z;
my $y = "3.14";

if (looks_like_number($z) and looks_like_number($y)) {
  say $z + $y;
}

say $z . $y;

if (defined $y) {
  say "defined";
} else {
  say "NOT";
}

перегрузка операторов

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