37. Обращающий метаоператор R в Perl 6

В Perl 6 есть метаоператор R, который меняет операнды у другого оператора. Как и другие метаоператоры, R модифицирует поведение одного из уже существующих операторов, как встроенных, так и определенных пользователем.

1

Проще всего начать с элементарного примера.

say 5 R- 3; # -2

Без R получилось бы 2, а с ним запись 5 R- 3 равносильна 3 - 5.

Еще один пример, с оператором %%, который сообщает, делимо ли одно число на другое.

say 3 R%% 10; # False
say 3 R%% 12; # True

2

Можно попробовать применить метаоператор в выражении с двумя и более операциями. Например:

say 10 R- 20 R- 40; # 10

Здесь в обоих случаях стоит оператор минус, так что в итоге мы получаем 40 - (20 - 10).

Подобные конструкции c R вполне можно завернуть в оператор редукции. Предыдущий пример более наглядно (если это слово здесь применимо) записывается так:

say [R-] 10, 20, 40; # 10

36. Оператор редукции в Perl 6

Оператор редукции (reduction operator) — это пара квадратных скобок, поставленных вокруг обычного оператора.

Разумеется, следует отличать оператор [ ] от обращения к элементу массива по индексу. Рассмотрим несколько полезных примеров, которые значительно улучшают читаемость кода и делают его компактным.

Оператор редукции всегда действует так, что следующие две строки кода эквивалентны:

[op] @array;

@array[0] op @array[1] op @array[2] op ... op @array[$N];

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

Арифметика

Задача 1: найти сумму всех элементов массива.

my @a = 1..100;
say [+] @a; # 5050

Задача 2: вычислить факториал.

say [*] 1..7; # 5040

Строки

Задача 3. Составить строку из частей.

my @s = < HE LL OWO RL D >;
say [~] @s; # HELLOWORLD

Сравнение

Задача 4. Определить, отсортирован ли массив по возрастанию.

my @n = (10, 20, 30, 40);
say [<] @n; # True

* * *

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

35. Минимум и максимум в Perl 6

В Perl 6 существуют операторы min и max для поиска минимума и максимума. Все очень просто и интуитивно:

say 5 min 10; # 5
say 5 max 10; # 10

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

say 4 min 2 min 10 min 5 min 3; # 2
say 4 max 2 max 10 max 5 max 3; # 10

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

say [min] 4, 2, 10, 5, 3; # 2
say [max] 4, 2, 10, 5, 3; # 10

Наконец, если есть список как объект, то на нем можно вызвать одноименные методы:

say (4, 2, 10, 5, 3).min; # 2
say (4, 2, 10, 5, 3).max; # 10

Это работает и со строками, но, разумеется, они сортируются как строки, независимо от смысла:

say <one two three four five six>.min; # five
say <one two three four five six>.max; # two

34. Оператор последовательности … в Perl 6

В Perl 6 существует оператор, создающий последовательности (sequence operator):

...

Не путайте его с оператором из двух точек для создания диапазонов.

Итак, рассмотрим основные варианты применения оператора из трех точек.

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

.say for 1...5;

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

.say for 1...5;

Однако, есть несколько отличий. Во-первых, тип созданного объекта:

(1...5).WHAT.say; # (Seq)
(1..5).WHAT.say;  # (Range)

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

.say for 1, 3 ... 11;    # 1 3 5 7 9 11

.say for 1, 2, 4 ... 64; # 1 2 4 8 16 32 64

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

.say for 1, 3 ... 10; # 1 3 5 7 9

Формируемые последовательности могут быть ленивыми, если в качестве границы указана звездочка:

(1...*).is-lazy.say; # True

В таком случае новые элементы генерируются по мере необходимости:

for 1, 2, 4 ... * -> $n {
    last if $n > 1000;
    say $n;
}

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

.say for 1, {$_ * 3} ... 243;

Эта программа печатает числа 1, 3, 9, 27, 81 и 243. Обратите внимание, что при таком подходе верхняя граница должна быть одним из вычисленных элементов последовательности. Если этого не соблюсти и поставить, например, произвольное большое число, то генератор последовательности проскочит его и продолжит бесконечно генерировать числа.

Вместо блока кода удобно воспользоваться звездочкой:

.say for 1, -* ... *; # 1 -1 1 -1 1 -1 1 -1 . . .

Ознакомьтесь также с заметкой «Цепочки последовательностей».

33. Инкремент строк в Perl 6

В целом заголовок противоречивый, но в Perl 6 операция инкремента и декремента вполне применима и к строкам:

my $s = 'World';

$s++;
say $s; # Worle

$s--;
say $s; # World

Если в строке были цифры, то начинается магия, и увеличивается именно число:

my $n = 'n48';
say $n.WHAT; # Str

say ++$n; # n49
say ++$n; # n50
say ++$n; # n51

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

my $n = 'n98';

say ++$n; # n99
say ++$n; # o00
say ++$n; # o01

Наконец, еще она хитрая приятность. Если строка похожа на имя файла, то Perl 6 проявит сообразительность и попытается изменить имя, но не расширение файла. Это удобно применять при создании множества нумерованных файлов:

my $filename = 'data000.csv';
say $filename++ for 1..5;

Получается именно то, что ожидается интуитивно:

data000.csv
data001.csv
data002.csv
data003.csv
data004.csv

P. S. Инкремент строк работает и в Perl 5, но имена файлов там изменить не получится: все сломается и получится 1. Мало того, попытка декремента строки превратит ее в –1.

32. Выбор случайного элемента в Perl 6

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

Это решается крайне просто: в Perl 6 определены методы pick и roll, которые выберут и вернут случайный элемент:

my @a = 'a' .. 'z';
say @a.pick; # b
say @a.roll; # u

Усложняем задачу: выбрать несколько случайных элементов.

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

my @a = 'a' .. 'z';
say @a.pick(5); # (b i c x v)
say @a.roll(5); # (c k m c f)

Уже на этом случайном результате видно, что roll вернул повторяющиеся элементы. Именно так и есть: pick заботится об уникальности возвращаемых данных, а roll — нет.

Из этого свойства вытекает важное ограничение: если запрошенный список длиннее оригинального, то метод pick вернет меньше запрошенного — возвращаемый список будет случайно пересортированным оригинальным.

my @b = 'a' .. 'd';

say @b.pick(10); # (c a b d)
say @b.roll(10); # (a c a c c a b a b b)

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

my @a = 'a' .. 'z';
say pick(3, @a); # (g v d)
say roll(3, @a); # (j w r)