39. 0 but True в Perl 6

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

# Perl 5
my $value = '0 but true';
say 2 + $value;     # 2
say 'OK' if $value; # OK

В Perl 6 такие трюки можно делать без привлечения строк — для этого есть инфиксный оператор but:

my $v = 0 but True;
say $v;  # 0
say ?$v; # True

Этот оператор подмешивает к объекту метод, имя которого совпадает с типом значения. В данном примере True это Bool, поэтому переменная $v получает метод Bool, возвращающий True. Теперь в булевом контексте (явно или неявно) переменная окажется истиной, хотя ее числовое значение продолжает быть нулем.

Аналогично можно подмешивать другие типы. Например, сказать, что число пи в виде строки это «примерно три»:

my $p = pi but 'примерно три';
say 0 + $p;
say "Значение равно $p";

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

3.14159265358979
Значение равно примерно три

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

$ perl6 -e'(0 but True).perl.say'
0

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

38. Тип данных Bag в Perl 6

Тип данных Bag — новый тип данных, которого не было в Perl 5.

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

Положим в сумку единицу и посмотрим, что про нее знает перл:

my $b1 = bag(1);
say $b1.perl;

Программа печатает такой ответ:

(1=>1).Bag

То есть, у нас есть одна единица.

А если положить еще двоечку:

my $b2 = bag(1, 2);
say $b2.perl;

Теперь там одна единица и одна двойка:

(1=>1,2=>1).Bag

ОК, а если добавить еще единицу?

my $b3 = bag(1, 2, 1);
say $b3.perl;

Теперь их две:

(1=>2,2=>1).Bag

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

my $b1 = bag 1;
my $b2 = bag 1, 2;
my $b3 = bag 1, 2, 1;

Более типично, наверное, хранить не числа, а строки, например:

my $cars = bag <green black blue black white>;

Что можно узнать про содержимое переменной $cars?

Во-первых, какие там присутствуют цвета:

say $cars.keys; # (white blue black green)

Во-вторых, сколько, собственно, разных цветов:

say $cars.elems; # 4

Либо сколько разных объектов:

say $cars.total; # 5

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

my $cars2 = bag('green' => 1, 'black' => 2, 'blue' => 1, 'white' => 1);

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 . . .

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