Выпуск 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 →