Выпуск 27. Май 2015

Отладка приложений на AnyEvent | Содержание | Метаоператоры в Perl 6

Операторы Perl 6. Часть 1

Обзор префиксных, постфиксных и инфиксных операторов Perl 6

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

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

Префиксы (prefixes)

!

! — логический оператор отрицания.

say !True;     # False
say !(1 == 2); # True

+

+ — унарный плюс, преобразующий свой операнд к числовому контексту. Выполняемое действие аналогично вызову метода Numeric:

my Str $price = '4' ~ '2';
my Int $amount = +$price;
say $amount;               # 42
say $price.Numeric;        # 42

Один из важных случаев применения унарного плюса встречался в статье про грамматики: +$/. Эта конструкция преобразует в число объект типа Match, который содержит данные о совпавшей части грамматики или регекса.

-

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

my Str $price = '4' ~ '2';
say -$price; # -42

?

? — оператор, преобразующий контекст в логический, вызывая на объекте метод Bool:

say ?42; # True

~

~ — оператор преобразования к строковому типу.

my Str $a = ~42;
say $a.WHAT; # (Str)

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

++

++ — префиксная форма оператора инкремента. Сначала выполняется инкремент, а потом возвращается новое значение.

my $x = 41;
say ++$x; # 42

Инкремент не обязан ограничиваться только числами:

my $a = 'a';
say ++$a; # b

Особый случай — имена файлов с порядковым номером внутри (текстовые строки, содержащие число, точку и расширение):

my $f = "file001.txt";

++$f;
say $f; # file002.txt

++$f;
say $f; # file003.txt

-- — префиксная форма оператора декремента. Как и префиксный ++, оператор сначала выполняет действие над операндом (декремент в данном случае), а затем возвращает результат.

my $x = 42;
say --$x; # 41

С текстовыми значениями поведение аналогично оператору ++.

+^

+^ — побитовое отрицание с учетом двоичного дополнения.

my $x = 10;
my $y = +^$x;
say $y; # -11 (не -10)

Этот оператор может выступать и в качестве бинарного (см. дальше). Ср. также с ?^.

?^

?^ — логическое отрицание. Важно обратить внимание, что это не инверсия битов: вначале аргумент преобразуется к булевому значению, а затем делается отрицание.

my $x = 10;
my $y = ?^$x;
say $y;       # False
say $y.WHAT;  # (Bool)

^

^ — оператор, создающий диапазон (объект типа Range) от нуля до указанного значения (не включая его).

.print for ^5; # 01234

Этот пример аналогичен более явному:

.print for 0..4; # 01234

|

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

sub sum($a, $b) {
    $a + $b
}

my @data = (10, 20);
say sum(|@data); # 30

Без оператора | компилятор сообщит об ошибке, поскольку функция ожидает два скаляра и не готова принять массив.

not

not преобразует операнд к логическому типу и делает отрицание. То же, что делает префиксный оператор !.

say not False; # True

so

so преобразует операнд к логическому типу и возвращает результат. Действие аналогично префиксу ?.

say so 42;   # True
say so True; # True
say so 0.0;  # False

temp

temp — делает переменную временной, значение которой восстановится при выходе за пределы текущей области видимости (как local в Perl 5).

my $x = 'x';
{
    temp $x = 'y';
    say $x;        # y
}
say $x;            # x

Ср. с префиксом let.

let

let — префиксный оператор, позволяющий восстановить значение переменной, если выход из текущей области видимости был выполнен с исключением.

my $var = 'a';
try {
    let $var = 'b';
    die;
}
say $var; # a

При наличии die пример напечатает исходное значение a, а если строку с die закомментировать, то выполненное в блоке присваивание сохранится, и на печати окажется b.

Ключевое слово let выглядит похоже на деклараторы my, our и другие, но является при этом префиксным оператором. Ср. с префиксом temp.

Постфиксы (postfixes)

++

++ — постфиксный вариант инкремента. Инкрементирует операнд, но, в отличие от префиксной формы, возвращает предыдущее значение.

my $x = 42;
say $x++;   # 42
say $x;     # 43

-- — постфиксный декремент. Уменьшает значение на единицу и возвращает предыдущее значение.

И постфиксные, и префиксные операторы инкремента и декремента имеют «магическое» (magic) свойство правильно обрабатывать числа в именах файла:

my $filename = 'file01.txt';
for 1..10 {
    say $filename++;
}

Этот пример печатает имена файлов file01.txt, file02.txt, …, file10.txt.

Постфиксы вызова методов (method postfixes)

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

.

.method вызывает метод method на переменной. Это работает как с настоящими методами классов, определенных пользователем, так и с объектами всех встроенных типов. Кроме того, при попытке вызвать метод на объекте нативного типа (такого как int — машинное представление целого числа), будет создан объект одного из встроенных типов (в примере с intInt), на котором и вызовется метод.

say "0.0".Numeric; # 0
say 42.Bool;       # True

class C {
    method m() {say "m()"}
}
my $c = C.new;
$c.m(); # m()

.=

.= является мутирующим вызовом метода на объекте. Действие $x.=method равнозначно записи $x = $x.method.

В следующем примере контейнер $o, первоначально содержащий объект класса C, после вызова $o.=m() замещается новым объектом, уже другого класса.

class D {
}

class C {
    method m() {
        return D.new;
    }
}

my $o = C.new;
say $o.WHAT;  # (C)

$o.=m();
say $o.WHAT;  # (D)

.^

.^method вызывает метод method, но не напрямую на текущем объекте, а на метаобъекте типа HOW. Следующие две записи эквивалентны:

my Int $i;
say $i.^methods();
say $i.HOW.methods($i);

Метаобъекты — отдельная тема, к которой есть смысл вернуться в следующий раз.

.?

.?method вызывает метод, если он определен. Если нет, возвращает Nil.

class C {
    method m() {'m'}
}

my $c = C.new();
say $c.?m();     # m
say $c.?n();     # Nil

.+

.+method делает попытку вызвать все методы с именем method, доступные для объекта. Такая ситуация может возникнуть при создании иерархий объектов.

class A {
    method m($x) {"A::m($x)"}
}
class B is A {
    method m($x) {"B::m($x)"}
}

my $o = B.new;
my @a = $o.+m(7);
say @a; # Печатается B::m(7) A::m(7)

В этом примере объект $o может обратиться к методу m либо из своего класса B, либо из родительского A. Конструкция $o.+m(7) выполняет вызовы обеих методов и помещает результаты в массив. Если метода с нужным именем не обнаружено, возникает исключение.

.*

.*method вызывает все методы с именем method, и возвращает парсел со списком результатов, либо пустой набор, если метод не определен. В остальном работает аналогично оператору .+.

Инфиксные операторы

Инфиксные операторы стоят в программе между операндами, и могут быть бинарными (в отличие от унарных префиксных или постфиксных) или тернарными (такой оператор всего один).

Простейший пример инфиксного оператора — символ сложения +. Справа и слева он ожидает два значения, например, две переменные: $a + $b. Важно понимать, что один и тот же символ или одна и та же последовательность символов в разных контекстах может быть или префиксом, или инфиксом. В примере с плюсом — это определенный в Perl 6 унарный плюс, устанавливающий числовой контекст: +$str.

Операторы для работы с числами

+, -, *, /

+, -, *, / — операторы, выполняющие соответствующие арифметические действия, они не требуют пояснения. При разговоре о Perl 6 нужно помнить, что прежде чем выполнить операцию операнды будут автоматически приведены к численному (Numeric) типу, если это необходимо.

div

div — целочисленное деление с округлением вниз.

say 10 div 3;  # 3
say -10 div 3; # 4

%

% — деление по модулю (остаток от целочисленного деления). Операнды при необходимости приводятся к численному типу.

mod

mod — деление по модулю, но в отличие от % приведение типов не выполняется. Сравните четыре примера.

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

say 10 % 3;   # 1
say 10 mod 3; # 1

Если один из операндов сделать строкой, то оператор % приведет ее к числу:

say 10 % "3"; # 1

А при использовании оператора mod возникнет ошибка:

say 10 mod "3";

Calling 'infix:<mod>' will never work with argument types (Int, Str)
    Expected any of: 
    :(Real $a, Real $b)

Поэтому преобразование необходимо выполнить явно:

say 10 mod +"3"; # 1

Или:

say 10 mod "3".Int; # 1

%%

%% — оператор, сообщающий о возможности целочисленного деления (divisibility, «делибельность») без остатка. Возвращает булевое значение.

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

+&, +|, +^

+&, +|, +^ — побитовое умножение, сложение и XOR. Плюс в операторе намекает на то, что операнды перед выполнением действия приводятся к типу Int.

?|, ?&, ?^

?|, ?&, ?^ приводят операнды к логическому типу и выполняют логические операции OR, AND и XOR.

+<, +>

+<, +> — операторы побитового сдвига влево или вправо.

say 8 +< 2; # 32
say 1024 +> 8; # 4

gcd

gcd (greatest common denominator) вычисляет наибольший общий делитель двух чисел.

say 50 gcd 15; # 5

lcm

lcm (least common multiple) находит наименьшее общее кратное.

say 1043 lcm 14; # 2086
    

==, !=

==, != выполняют сравнение двух численных операндов. Данные приводятся к типу Numeric при необходимости.

<, >, <=, >=

<, >, <=, >= — операторы для численного сравнения.

<=>

<=> — оператор для сравнения чисел, возвращающий значение типа Order: Order::Less, Order::More или Order::Same.

Операторы для работы со строками

~

~ — конкатенация строк. Точка в Perl 6 теперь используется для вызова методов, а все действия, связанные со строками, используют тильду.

say "a" ~ "b"; # ab

При необходимости происходит преобразование типов:

say "N" ~ 1; # N1
say 4 ~ 2;   # 42

x

x повторяет строку указанное число раз.

say "A" x 5; # AAAAA

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

say 0 x 5; # 0000

Если запрошено отрицательное число повторов, возвращается пустая строка.

eq, ne

eq, ne сравнивают строки (или приведенные к строке значения) на равенство и неравенство, соответственно.

lt, gt, le, ge

lt, gt, le, ge — операторы для сравнения строк: меньше, больше, меньше или равно, больше или равно. Операнды приводятся к строкам.

leg

leg — оператор, сообщающий, равны ли строки, либо одна из них «больше» или «меньше» (в алфавитном порядке). Аналогична оператору cmp из Perl 5 (но не из Perl 6), но возвращает значение Order::Less, Order::More или Order::Same.

say "a" leg "b";        # Less
say "abc" leg "b";      # Less
say "bc" leg "b";       # More
say "abc" leg "ABC".lc; # Same

Перед сравнением все операнды приводятся к строкам.

say 42 leg "+42"; # More
say 42 leg "42";  # Same

Универсальные операторы сравнения

В Perl 6 определены несколько операторов, которые одинаково подходят для сравнения как строк, так и для чисел и даже для составных объектов (например, пар).

cmp

cmp сравнивает два объекта и возвращает значение типа Order — одно из Less, Same и More.

say 2 cmp 2;   # Same
say 2 cmp 2.0; # Same
say 1 cmp 2;   # Less
say 2 cmp 1;   # More

say "a" cmp "b";        # Less
say "abc" cmp "b";      # Less
say "bc" cmp "b";       # More
say "abc" cmp "ABC".lc; # Same

my %a = (a => 1);
my %b = (a => 1);
say %a cmp %b; # Same

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

proto sub infix:<cmp>(Any, Any) returns Order:D is assoc<none>
multi sub infix:<cmp>(Any,       Any)
multi sub infix:<cmp>(Real:D,    Real:D)
multi sub infix:<cmp>(Str:D,     Str:D)
multi sub infix:<cmp>(Enum:D,    Enum:D)
multi sub infix:<cmp>(Version:D, Version:D)

(:D в определении — это не смайл, а указание на то, что аргумент должен быть определен, то есть в переменной этого типа должно содержаться значение.)

Поэтому при сравнении строки с числом компилятор скорее всего выберет вариант функции с сигнатурой (Str:D, Str:D), и оба операнда будут рассматриваться как строки:

say "+42" cmp +42; # Less
say ~42 cmp +42;   # Same

Обратите внимание на отличие этого оператора от одноименного, доступного в Perl 5. Ср. с оператором leg.

before, after

before, after — универсальные операторы сравнения, работающие с числами, строками и объектами других типов. Возвращает логическое значение True или False в зависимости от того, какой из операндов стоит раньше или позже.

Работа с преобразованиями типов операндов аналогична оператору cmp. Стоит иметь в виду, что в зависимости от типа данных сравнение либо чисел, либо «таких же» строк в разных случаях может привести к противоположным результатам, поскольку числа сравниваются как числа, а строки — как строки в алфавитном порядке:

say 10 before 2;      # False
say '10' before '2';  # True

say 10 before 20;     # True
say '10' before '20'; # True

eqv

eqv — оператор, сравнивающий объекты на эквивалентность. Возвращает булевое значение, истинное в том случае, если оба операнда имеют одинаковый тип и содержат одинаковые данные.

my $x = 3;
my $y = 3;
say $x eqv $y; # True

Пример с более сложными данными:

my @a = (3, 4);
my @b = (3, 4, 5);
@b.pop;
say @a eqv @b; # True

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

say 42 eqv 42.0; # False
say 42 eqv "42"; # False

Важно не попасть в ловушку, когда в дело вступает тип Rat:

say 42 eqv 84/2;       # False
say 42 eqv (84/2).Int; # True

===

=== — оператор, возвращающий истинное значение в том случае, если операнды являются одним объектом, и ложное в остальных случаях.

class I {
}

# Три разных экземпляра.
my $i = I.new;
my $ii = I.new;
my $iii = I.new;

my @a = ($i, $ii, $iii);
for @a -> $a {
    for @a -> $b {
        say $a === $b; # Печатает True только там, где $a и $b
                       # указывают на один и тот же элемент
                       # массива @a.
    }
}

=:=

=:= — оператор для проверки того, что операнды ссылаются на один и тот же объект. Возвращается истина или ложь.

my $x = 42;
my $y := $x;

say $x =:= $y; # True
say $y =:= $x; # True

~~

~~ — оператор смартматчинга. Оператор выполняет сравнение объектов и на первый взгляд работает независимо от их типа, например:

say 42 ~~ 42.0; # True
say 42 ~~ "42"; # True

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

say "42.0" ~~ 42; # True
say 42 ~~ "42.0"; # False

Это поведение проясняется, если рассмотреть алгоритм работы оператора ~~. Оператор вычисляет значение правого операнда и вызывает на нем метод ACCEPTS, которому передает значение левого операнда (точнее, ссылку $_ на него). Для каждого типа данных существует свой вариант метода ACCEPTS. Для строк, например, он сравнивает строки, а для чисел — числа.

Предыдущие два примера эквивалентны следующим:

say 42.ACCEPTS("42.0"); # True
say "42.0".ACCEPTS(42); # False

Операторы для работы со списками

xx

xx повторяет список заданное число раз.

say((1, -1) xx 2); 1 -1 1 -1

Аналогично строковому x, оператор xx возвращает пустой список, если число повторов отрицательное или нулевое.

Z

Z — зип-оператор (zip). Смешивает два массива, возвращая по очереди элементы то одного, то другого из них до тех пор, пока в каждом из списков достаточно элементов.

Запись

@c = @a Z @b;

эквивалентна примерно следующей:

@c = (@a[0], @b[0], @a[1], @b[1], ...);

X

X — оператор, формирующий из двух списков третий, состоящий из всех возможных комбинаций элементов исходных массивов.

@c = @a X @b;

То же, что:

@c = (@a[0], @b[0], @a[0], @b[1], @a[0], @b[2], ..., @a[N], @b[0], @a[N], @b[1] ..., @a[N], @b[M]);

Операнды не обязательно должны быть одинаковой длины.

... — оператор, создающий ленивые списки.

my @list = 1 ... 10;

В отличие от .., три точки умеют считать и в обратную сторону:

my @back = 10 ... 1;

Звездочка в качестве конечного элемента создаст бесконечный ленивый список:

my @long = 1 ... *;

Операторы для объединений

|, &, ^

|, &, ^ — операторы, создающие junctions (объединения? они же ранее известны как «квантовые суперпозиции»). Эти объекты могут использоваться на месте скаляров, но ведут себя одновременно как несколько значений.

Операторы |, & и ^ создают, соответственно, junctions типа any, all и one. Junctions ведут себя как скаляры, содержащие одновременно несколько значений:

# Значение 4 – одно из перечисленных:
say "ok" if 4 == 1|2|3|4|5; 

# 4 нет ни в каком из указанных:
say "ok" if 4 != 1 & 2 & 3 & 5; 

# 4 повторяется дважды, поэтому оно не единственное:
say "ok" unless 4 == 1 ^ 2 ^ 2 ^ 4 ^ 4 ^ 5;

Shortcut-операторы

&&

&& возвращает первый из операндов, который будучи преобразованным в логический тип становится False. Обратите внимание, что возвращаемое значение — не True или False, а оригинальное значение одного из операндов (если конечно он уже не был False).

say 10 && 0; # 0
say 0 && 10; # 0;

say 12 && 3.14 && "" && "abc"; # пустая строка

Как только найдено подходящее значение, работа оператора закончится, и все оставшиеся справа значения вычисляться не будут.

||

|| возвращает первый операнд, который в логическом контексте является True. Последующие значения не вычисляются.

say 10 || 0; # 10
say 0 || 10; # 10

^^

^^ — возвращает операнд, который является в булевом контексте истинным, и при этом он такой единственный. Если ничего не найдено, возвращается Nil. Как только найден второе истинное значение, вычисления оставшихся прекращаются.

say 0 ^^ '' ^^ "abc" ^^ -0.0;  # abc
say 0 ^^ '' ^^ "abc" ^^ -10.0; # Nil

//

// возвращает первый определенный (defined) операнд. Остальные при этом не вычисляются.

my $x;
my $y = 42;
my $z;
say $x // $y // $z; # 42

Другие инфиксные операторы

min, max

min, max возвращают, соответственно, минимальный и максимальный из своих операндов.

?? !!

?? !! — тернарный условный оператор. Работает как ? : в Perl 5.

say rand < 0.5 ?? 'Yes' !! 'No';

=

= — оператор присваивания.

=>

=> — конструктор пар. Создает пары ключ — значение, причем ключ не обязательно заключать в кавычки.

my $pair = alpha => "one";

my %data = jan => 31, feb => 28, mar => 31;

,

, — оператор конструирования парселов (parcel).

my $what = (1, 2, 3);
say $what.WHAT; # (Parcel)

При вызове функций запятая используется как разделитель передаваемых параметров.

:

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

class C {
    method meth($x) {
        say "meth($x)";
    }
}
my $o = C.new;
meth($o: 42); # Вызывается метод meth объекта $o, печатается meth(42)

После инфиксного двоеточия обязательно должен быть пробел (если его нет, двоеточие может означать именованный параметр), и оно должно стоять только после первого аргумента.

Андрей Шитов


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

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