Выпуск 16. Июнь 2014

От редактора | Содержание | Что нового в Perl 5.20.0

Синтаксические новинки в Perl 5.20

Рассказ о том, что нового появилось в синтаксисе.

27 мая вышел очередной стабильный релиз за номером 5.20. Подробное описание всех сделанных изменений доступно в файле perldelta, его перевод опубликован в этом номере журнала. В этой же статье я более подробно расскажу про изменения, которые были внесены в синтаксис языка.

Сигнатуры

(О сигнатурах журнал уже писал в одном из предыдущих выпусков.)

Формальные аргументы функций (подпрограмм sub) теперь возможно перечислять, причем вместе с именами, непосредственно в заголовке функции. Например:

sub fahrenheit2celsius($temperature) {
    return 5 / 9 * ($temperature - 32);
}

Аргумент, переданный первым параметром, внутри тела функции становится доступным под именем $temperature. Дополнительно объявлять эту переменную не требуется (обратите внимание на отсутствие ключевого слова my).

Использование функции ничем не отличается от обычного способа:

say fahrenheit2celsius(100);

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

use v5.20;

use feature 'signatures';
no warnings 'experimental::signatures';

sub fahrenheit2celsius($temperature) {
   return 5 / 9 * ($temperature - 32);
}

say fahrenheit2celsius(100);

Директива use feature 'signatures' включает сигнатуры (несмотря на наличие use v5.20, автоматически этого не происходит), а no warnings 'experimental::signatures' подавляет сообщение The signatures feature is experimental at signatures.pl line 6.

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

sub fahrenheit2celsius($temperature) {
   $temperature -= 32;
   return 5 / 9 * $temperature;
}

my $F = 100;
my $C = fahrenheit2celsius($F);
say "$F F = $C C";

Использование сигнатур никак не отражается на обоработке массива @_. Поэтому вполне допустимо (хотя и непонятно, зачем) использовать его для доступа к переданным аргументам:

sub fahrenheit2celsius($temperature) {
   my ($t) = @_;
   return 5 / 9 * ($t - 32);
}

Использование оператора shift для чтения аргументов из массива @_ тоже никак не затрагивает переменную $temperature, указанную в заголовке. Переданный аргумент исчезнет из @_, но останется в $temperature.

Однако, коль скоро функция объявлена с перечнем аргументов, все попытки ее использовать в коде будут проверяться на наличие аргументов и их правильного числа. Несовпадение числа аргументов породит ошибку на этапе выполнения программы Too many arguments for subroutine или Too few arguments for subroutine.

Примечение. Ошибка возникает именно на этапе выполнения, а не при компиляции. В этом легко убедиться, напечатав что-нибудь до ошибочного вызова функции:

sub f($arg) {
    say "Never reached";
}

say "Start";
f(10, 20);

Аналогично, допустимо объявить и требовать при вызове пустой список параметров:

sub justcallme() {
    say "Called";
}

В этом случае безошибочным будет только вызов без аргументов: justcallme(); или без скобок: justcallme;.

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

sub print_envelope_label(
    $name, $last_name,
    $street, $house_number,
    $city = 'Moscow', $country = 'Russia') {

    say "$name $last_name\n$street, $house_number\n$city, $country";
}

Если в этом примере не указать город и страну, будет напечатана этикетка для конверта с адресом в Дефолт-сити:

print_envelope_label(
    'Konstantin', 'Konstantinopolsky',
    'Konstantinovskaya', 10,
    'Kiev', 'Ukraine'
);

print_envelope_label(
    'Ivan', 'Ivanov',
    'Tverskaya', 4
);

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

В том случае, если необходимо передать произвольное число аргументов, допустимо воспользоваться переменной-списком или хешем:

sub numerate_list(@list) {
    my $c = 1;
    for my $value (@list) {
        say "$c. $value";
        $c++;
    }
}

numerate_list('alpha', 'beta', 'gamma');

Вариант с хешом:

sub dump_hash(%hash) {
    for my $key (sort keys %hash) {
        say "$key = $hash{$key}";
    }
}

dump_hash(a => 'alpha', b => 'beta', c => 'gamma');

Опять же, такие аргументы должны следовать в конце списка аргументов. Идеологически эти варианты похожи на традиционное чтение аргументов из массива @_.

Наконец, если в списке формальных аргументов окажутся переменные без имен (то есть голые сигилы $, @ или %), то соответствующие аргументы окажутся обязательными, но внутри функции их значения по именам доступны не будут. Такая возможность окажется полезной, например, при написании тестов или эмулировании некоего интерфейса:

sub mock_this($key, $, $value) {
    say "$key = $value";
}

mock_this(1, 2, 3);

Несмотря на то, что второй аргумент по имени недоступен, его по-прежнему возможно прочитать стандартным способом: say $_[1].

Прототипы

Еще одно изменение, связанное с объявлением функций, — новое ключевое слово prototype. Синтаксис описания прототипа похож на синтаксис атрибутов функций:

use v5.20;

use feature 'signatures';

sub sum :prototype($$) {
    my ($x, $y) = @_;
    return $x + $y;
}

say sum(10, 20);

Похоже, что отдельное ключевое слово появилось из-за того, что включение в язык сигнатур сделало неоднозначным объявления типа sub f($). С одной стороны, это функция, которая принимает один неиспользуемый скаляр (с точки зрения сигнатуры), а с другой — обязятельный скаляр (с точки зрения традиционных прототипов). В этом примере это одно и то же, но тем не менее, при наличии директивы use feature 'signatures'; традиционный синтаксис описания прототипа (когда в скобках за именем функции без запятых или после точки с запятой перечисляются сигилы формальных параметров) перестает работать и компилятор сообщает об ошибке:

Parse error at prototype_error.pl line 6.
syntax error at prototype_error.pl line 6, near "$$) "

Прототипы в новом виде совместимы с сигнатурами. Показанный пример с функцией sum в этом случае будет выглядеть таким образом, что позволяет сэкономить одну строку кода:

sub sum :prototype($$) ($x, $y) {
    return $x + $y;
}

Именованные аргументы должны быть указаны только после объявления прототипа, непосредственно перед блоком с телом функции.

Казалось бы, указывать прототипы одновременно с сигнатурами избыточно. Однако, в некоторых случаях это может пригодиться. Например, функция с сигнатурой умеет разворачивать переданные ей списки. Поэтому, следующий пример полностью работоспособен, хотя функции передан массив вместо двух скаляров:

sub sum($x, $y) {
    return $x + $y;
}

my @values = (10, 20);
say sum(@values);

Уточнение sub sum :prototype($$) ($x, $y) запрещает такое поведение. Если настаивать на передаче списка, то Perl сообщит об ошибке:

Not enough arguments for main::sum at prototype4.pl line 15, near "@values)"
Execution of prototype4.pl aborted due to compilation errors.

Обратите внимание, что ошибка произошла во время компиляции (однако, безошибочный код в блоке BEGIN по-прежнему может быть выполнен).

Читателю предоставляется возможность самостоятельно разобраться в том, как изменились правила в описании прототипа, связанные с наличием пробелов, точек за запятой и звездочками.

Срезы с индексами

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

Новое заключается в том, что используется обычный синтаксис среза, но при этом на месте сигила стоит символ %. И для хешей, и для массивов. После сигила следует имя соответствующей переменной, а затем в фигурных или квадратных скобках перечисляются ключи или индексы. Например:

my %suffix = (pl => 'Perl', pm => 'Perl module', p6 => 'Perl 6');
my @data = %suffix{'pl', 'p6'};
say Dumper(\@data);

my @greek = qw(alpha beta gamma delta epsilon);
@data = %greek[1, 2];
say Dumper(\@data);

Выражение %suffix{'pl', 'p6'} вернет список ключей и значений хеша %suffix:

$VAR1 = [
          'pl',
          'Perl',
          'p6',
          'Perl 6'
        ];

А выражение %greek[1, 2] — индексы и соответствующие значения:

$VAR1 = [
          1,
          'beta',
          2,
          'gamma'
        ];

Если намеренно или ошибочно поставить сигил списка: @suffix{'pl', 'p6'} и @greek[1, 2], то программа останется синтаксически верной, но работать будет совершенно иначе:

$VAR1 = [
          'Perl',
          'Perl 6'
        ];

$VAR1 = [
          'beta',
          'gamma'
        ];

(Впрочем, ошибочный сигил $ в обоих случаях тоже будет успешно скомпилирован, даже включенный use warnings найдет не все.)

Андрей Шитов


От редактора | Содержание | Что нового в Perl 5.20.0
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье