44. Транспонирование матрицы в Perl 6

В Perl 6 есть метаоператор Z, который объединяет два списка как застежка на молнии, по очереди перемежая элементы. Мы, возможно, рассмотрим его как-нибудь отдельно, но сегодня я хочу показать, как этот оператор магически транспонирует матрицу.

Берем двумерную матрицу:

my @matrixA = [1, 2],
              [3, 4];

Если хочется, можно поставить еще одни скобки:

my @matrixA = [[1, 2],
               [3, 4]];

И теперь в одно действие транспонируем:

my @matrixB = [Z] @matrixA;

Метаоператор Z здесь поставлен внутрь оператора редукции. Вуаля, матрица транспонировалась:

[(1 3) (2 4)]

43. Массивы как аргументы функций в Perl 6

В Perl 6 передавать массивы функциям одно удовольствие. Достаточно объявить массив в сигнатуре, и Perl поймет, что с этим делать дальше. В том числе, если дальше идут, например, скаляры.

sub f($a, @b, $c) {
    say "a = $a";
    say "b = @b[]";
    say "c = $c";
}

my @arr = <5 7 9>;
f(10, @arr, 20);

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

a = 10
b = 5 7 9
c = 20

Очень удобно, если сравнивать с Perl 5.

Но теперь возникает другой вопрос: а как передать переменное число скалярных величин так, чтобы они все оказались в одном массиве? Ответ: с помощью звездочки, которая создает так называемый slurpy-параметр.

sub g(*@data) {
    say @data;
}

g(3, 5, 7);

Эта программа работает правильно:

[3 5 7]

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

===SORRY!=== Error while compiling sub-slurpy.pl
Calling g(Int, Int, Int) will never work with declared signature (@data)
at sub-slurpy.pl:5
------> <BOL>⏏g(3, 5, 7);

42. Фейзеры ENTER и LEAVE в Perl 6

Блоки, которые помечаются большими буквами, в Perl 6 называются фейзерами (phasers). Вы знакомы с ними по Perl 5, например: BEGIN и END.

Сегодня мы рассмотрим два фейзера, которые выполняются при входе в подпрограмму и при выходе из нее: ENTER и LEAVE. В следующем примере это наглядно видно:

sub f() {
   ENTER say 'Hi';
   LEAVE say 'Bye';

   say 'Body';
}

f;

Программа печатает такие строки:

Hi
Body
Bye

То есть сначала выполняется блок ENTER, затем тело функции и наконец блок LEAVE.

Порядок выполнения фейзеров не зависит от того, где они расположены. Например, все работает даже после return:

sub f() {
    say 'Body';
    return;

    LEAVE say 'Bye';
    ENTER say 'Hi';
}

При наличии более одного фейзера того же типа, блоки ENTER выполняются в порядке объявления, а LEAVE в противоположном:

sub f() {
    say 'Body';

    ENTER say 'Hi 1';
    ENTER say 'Hi 2';

    LEAVE say 'Bye 1';
    LEAVE {
        say 'Bye 2';
    }
}

f;

(Здесь одновременно показан пример с блоком кода в одном из фейзеров.)

Программа печатает строки в следующем порядке:

Hi 1
Hi 2
Body
Bye 2
Bye 1

41. Передача именованных аргументов в Perl 6

Функции (и методы классов) в Perl 6 способны принимать именованные параметры. Простейший способ — разделить имя и значения стрелкой:

sub f(:$a, :$b) {
    $a ** $b
}

say f(a => 2, b => 3); # 8

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

:key(value)

Все начинается с двоеточия, а значения стоят в скобках:

say f(:a(2), :b(3));

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

say f(:a<2>, :b<3>);

:Nkey

Для целочисленных величин предусмотрен еще вот такой странный синтаксис:

say f(:2a, :3b);

:key и :!key

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

sub g(:$key) {
    $key
}

say g(:key);  # True
say g(:!key); # False

Пары

Фактически, все показанные варианты создают пары ключ — значение, поэтому таким же образом можно поступить при создании хешей, например:

my %data = :alpha(10), :beta(20), :gamma(30);
say %data<beta>; # 20

40. Предопределенные символьные классы в регексах Perl 6

Давайте сегодня посмотрим символьные классы, которые доступны в регексах Perl 6.

Прежде всего, шпаргалка о том, как матчить строку:

'string' ~~ /str/;

Сегодня рассмотрим символьные классы, которые записываются с обратным слешем. Часть из них совпадает с тем, что есть в Perl 5.

Белое на белом

Существует несколько классов, совпадающих с разными видами пробельных символов:

\s — любой пробельный символ, как горизонтальный, так и вертикальный;

\h — горизонтальный пробел (например, пробел или табуляция);

\v — вертикальный пробел (например, перевод строки);

\n — перевод строки;

\t — табуляция.

Обратите внимание, как работают символы при сравнении с переводом строки:

say 1 if "\n" ~~ /\s/; # 1
say 2 if "\n" ~~ /\v/; # 2
say 3 if "\n" ~~ /\h/; # не совпало

Ко всем классам существуют комплиментарные, которые записываются с большой буквой: \S, \H, \V, \N и \T — все они совпадают с соответствующими типами не-пробелов.

Черное на белом

Пара символьных классов, совпадающих с конкретными наборами символов:

\d — цифры (не только ASCII, но и все юникодные).

\w — символы, которые могут быть в словах — буквы, цифры и символ подчеркивания.

Классы с противоположным смыслом:

\D — не цифры;

\W — не то, из чего состоят слова.

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