Выпуск 25. Март 2015

Мутационное тестирование | Содержание | Модули в Perl 6

Про переменные и сигнатуры в Perl 6

В этой статье описаны интересные синтаксические возможности Perl 6, о которых не было упомянуто в прошлый раз

Обновитесь

21 февраля появился релиз Rakudo Star 2015.02. Одновременно объявлено, что это последний релиз с поддержкой Parrot. Дальнейшая разработка будет вестись исключительно под виртуальную машину MoarVM.

Лексические переменные

Лексические переменные в Perl 6 ведут себя так, как и должны :-) Если попытаться использовать пременную вне блока, в котором она определена, возникнет ошибка. В Perl 5 ошибка возникнет лишь при наличии use strict или при указании версии, в которой strict заработает сам, например: use v5.12. В Perl 6 все работает сразу, и при попытке обратиться к переменной вне области ее видимости появится ошибка Variable '$x' is not declared.

{
    my $x = 42;
    say $x; # OK
}

#say $x; # Не OK

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

sub seq($init) {
    my $c = $init;

    return {$c++};
}

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

my $a = seq(1);

say $a(); # 1
say $a(); # 2
say $a(); # 3

Возможно создать две независимые копии локальной переменной:

my $a = seq(1);
my $b = seq(42);

say $a(); # 1
say $a(); # 2
say $b(); # 42
say $a(); # 3
say $b(); # 43

state-переменные

Отдельно нужно упомянуть state-переменные. Они появились в Perl 5.10 и работают так же, как и в Perl 6. Переменная, объявленная с ключевым словом state внутри функции, инициализируется при первом вызове и сохраняет значение при повторных обращениях к функции.

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

sub seq($init) {
    state $c = $init;

    return {$c++};
}

Сколько бы не создавалось счетчиков:

my $a = seq(1);
my $b = seq(42);

Все они будут являться одним и тем же:

say $a(); # 1
say $a(); # 2
say $b(); # 3
say $a(); # 4
say $b(); # 5

Динамические переменные

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

Динамические переменные помечаются вторым сигилом (твигилом) * (с намеком на wildcard).

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

sub alpha {
    my $*var = 'Alpha';
    echo();
}

sub beta {
    my $*var = 'Beta';
    echo();
}

sub echo() {
    say $*var;
}

alpha(); # Alpha
beta();  # Beta

Анонимные блоки

В Perl 6 есть понятие arrow blocks (или pointy blocks) — это такие анонимные блоки-замыкания, которые возвращают ссылку на функцию и могут принимать аргументы.

Синтаксически они снабжаются стрелкой ->, за которой следует список аргументов и блок кода:

my $cube = -> $x {$x ** 3};
say $cube(3); # 27

Здесь сначала создается блок {$x ** 3}, принимающий один аргумент $x, а затем делается вызов, аналогичный вызову функции через указатель на нее: $cube(3).

Такие блоки со стрелкой удобно использовать в циклах:

for 1..10 -> $c {
    say $c;
}

Фактически, for здесь принимает список 1..10 и блок кода с аргументом $c, хотя сначала может показаться, что эта конструкция — синтаксис для циклов. В следующем разделе мы вернемся к этому примеру, но уже без использования явной переменной (и поэтому без стрелки).

Список аргументов вполне может состоять и более чем из одного элемента:

my $pow = -> $x, $p {$x ** $p};
say $pow(2, 15); # 32768

То же самое применимо и к спискам:

for 0..9 -> $i, $j {
    say $i + $j;
}

В этом случае за один проход цикл будет поглощать сразу по два значения. Тело цикла отработает пять раз, печатая попарно сумму соседних цифр: 1, 5, 9 и т.д.

Плейсхолдеры

При создании анонимного блока кода, даже такого, который будет принимать аргументы, объявлять их необязательно. Perl 6 разрешает сразу использовать их подобно предопределенным переменным $a и $b в Perl 5.

В Perl 6 такие переменные должны быть снабжены твигилом ^, а порядок формальных аргументов будет соответствовать алфавитному порядку.

my $pow = {$^x ** $^y};
say $pow(3, 4); # 81

Фактические значения 3 и 4 окажутся, соответственно, в переменных $^x и $^y.

А теперь вернемся к циклу и перепишем его тело без использования явных аргументов:

for 0..9 {
    say $^n2, $^n1;
}

Обратите внимание, что, во-первых, блок кода начинается сразу без предваряющей его стрелки, а переменные $^n1 и $^n2 упоминаются в коде не в алфавитном порядке, но при этом получают правильные значения, как если бы они были указаны в сигнатуре функции в виде ($n1, $n2).

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

my $pow = {$:base ** $:exp};
say $pow(:base(25), :exp(2)); # 625

Порядок значений при вызове функции теперь не имеет значения. Следующий вызов вернет тот же результат:

say $pow(:exp(2), :base(25)); # 625

Переопределение функций

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

multi sub twice(Int $x) {
    return $x * 2;
}

multi sub twice(Str $s) {
    return "$s, $s";
}

say twice(42); # 84
say twice("hi"); # hi, hi

Работа этого примера довольно очевидна: когда передано целочисленное значение, вызывается twice(Int), а когда строка — twice(Str).

Переопределение с подтипами

Еще более интересное переопределение можно сделать, использовав подтипы. Подтипы в Perl 6 создаются с помощью ключевого слова subset. На основе одного из существующих типов возможно создать более узкий тип, допустимые значения которого будут проверяться условием, описанным в определении подтипа.

В следующем примере созданы два подтипа: для четных и нечетных целых чисел. Условие отбора явно определено в блоке после ключевого слова where.

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

subset Odd of Int where {$^n % 2 == 1};
subset Even of Int where {$^n % 2 == 0};

multi sub testnum(Odd $x) {
    say "$x is odd";
}

multi sub testnum(Even $x) {
    say "$x is even";
}

Теперь при вызове функции с именем testnum будет выбрана одна из двух: testnum(Even) для четных чисел и testnum(Odd) для нечетных:

for 1..4 -> $x {
    testnum($x);
}

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

1 is odd
2 is even
3 is odd
4 is even

Продолжение следует

В этом номере журнала вас еще ждет статья о модулях в Perl 6, а в следующем выпуске читайте о промисах (promises) и подробный рассаказ о том, как работать с грамматиками (grammar).

Андрей Шитов


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

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