Операторы
Crystal поддерживает множество операторов с одним, двумя или тремя операндами.
Выражения с операторами фактически разбираются как вызовы методов. Например, a + b семантически эквивалентно a.+(b) — вызову метода + для a с аргументом b.
Однако для синтаксиса операторов предусмотрены некоторые особые правила:
- Точку (
.), обычно ставящуюся между получателем и именем метода (то есть оператором), можно опустить. - Цепочки вызовов операторов компилятор перестраивает, чтобы обеспечить приоритет операторов. Приоритет гарантирует, что выражение, например
1 * 2 + 3 * 4, будет разобрано как(1 * 2) + (2 * 3)в соответствии с обычными математическими правилами. - Имена обычных методов должны начинаться с буквы или символа подчёркивания, тогда как операторы состоят только из специальных символов. Любой метод, имя которого начинается не с буквы или символа подчёркивания, является операторным методом.
- Допустимые операторы перечислены в белом списке компилятора (см. Список операторов ниже). Это позволяет использовать имена методов, состоящие только из символов, и обрабатывать их как операторы, включая правила приоритета.
Операторы реализуются как обычные методы, а стандартная библиотека предлагает множество таких реализаций, например для математических выражений.
Определение операторных методов
Большинство операторов можно реализовать как обычные методы.
Операторам можно придать любой смысл, но рекомендуется придерживаться семантики, близкой к общепринятому значению оператора, чтобы не создавать запутанный код, который трудно понять и который ведёт себя неожиданно.
Некоторые операторы определены непосредственно в компиляторе и не могут быть переопределены в пользовательском коде. К ним относятся оператор инверсии !, оператор присваивания =, составные операторы присваивания, например ||=, и операторы диапазона. Возможность переопределения метода указана в столбце Можно переопределить в таблицах операторов ниже.
Унарные операторы
Унарные операторы записываются в префиксной нотации и имеют только один операнд. Поэтому реализация метода не получает аргументов и оперирует только с self.
В следующем примере тип Vector2 представлен как двумерный вектор с методом унарного оператора - для инверсии вектора.
struct Vector2
getter x, y
def initialize(@x : Int32, @y : Int32)
end
# Unary operator. Returns the inverted vector to `self`.
def - : self
Vector2.new(-x, -y)
end
end
v1 = Vector2.new(1, 2)
-v1 # => Vector2(@x=-1, @y=-2)
Бинарные операторы
Бинарные операторы имеют два операнда. Поэтому реализация метода получает ровно один аргумент, представляющий второй операнд. Первый операнд является получателем self.
В следующем примере тип Vector2 представлен как двумерный вектор с методом бинарного оператора + для сложения векторов.
struct Vector2
getter x, y
def initialize(@x : Int32, @y : Int32)
end
# Binary operator. Returns *other* added to `self`.
def +(other : self) : self
Vector2.new(x + other.x, y + other.y)
end
end
v1 = Vector2.new(1, 2)
v2 = Vector2.new(3, 4)
v1 + v2 # => Vector2(@x=4, @y=6)
Согласно соглашению, тип возвращаемого значения бинарного оператора должен совпадать с типом первого операнда (получателя), чтобы typeof(a <op> b) == typeof(a). Иначе оператор присваивания (a <op>= b) непреднамеренно изменил бы тип a. Однако возможны обоснованные исключения. Например, в стандартной библиотеке оператор деления с плавающей точкой / для целочисленных типов всегда возвращает Float64, поскольку частное не должно ограничиваться диапазоном значений целых чисел.
Тернарные операторы
Условный оператор (? :) — единственный тернарный оператор. Он не разбирается как метод, и его значение нельзя изменить. Компилятор преобразует его в выражение if.
Приоритет операторов
Список отсортирован по приоритету: операторы в верхних строках имеют более высокий приоритет, чем операторы в нижних.
| Категория | Операторы |
|---|---|
| Индексация |
[], []?
|
| Унарные |
+, &+, -, &-, !, ~
|
| Возведение в степень |
**, &**
|
| Мультипликативные |
*, &*, /, //, %
|
| Аддитивные |
+, &+, -, &-
|
| Сдвиг |
<<, >>
|
| Побитовое И | & |
| Побитовое ИЛИ/исключающее ИЛИ |
|,^
|
| Равенство |
==, !=, =~, !~, ===
|
| Сравнение |
<, <=, >, >=, <=>
|
| Логическое И | && |
| Логическое ИЛИ | || |
| Диапазон |
.., ...
|
| Условный | ?: |
| Присваивание |
=, []=, +=, &+=, -=, &-=, *=, &*=, /=, //=, %=, |=, &=,^=,**=,<<=,>>=, ||=, &&=
|
| Распаковка |
*, **
|
Список операторов
Арифметические операторы
Унарные
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
+ | унарный плюс | +1 | да | правая |
&+ | циклический унарный плюс | &+1 | да | правая |
- | унарный минус | -1 | да | правая |
&- | циклический унарный минус | &-1 | да | правая |
Мультипликативные
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
** | возведение в степень | 1 ** 2 | да | правая |
&** | циклическое возведение в степень | 1 &** 2 | да | правая |
* | умножение | 1 * 2 | да | левая |
&* | циклическое умножение | 1 &* 2 | да | левая |
/ | деление | 1 / 2 | да | левая |
// | деление с округлением вниз | 1 // 2 | да | левая |
% | остаток от деления | 1 % 2 | да | левая |
Аддитивные
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
+ | сложение | 1 + 2 | да | левая |
&+ | циклическое сложение | 1 &+ 2 | да | левая |
- | вычитание | 1 - 2 | да | левая |
&- | циклическое вычитание | 1 &- 2 | да | левая |
Другие унарные операторы
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
! | инверсия | !true | нет | правая |
~ | побитовое дополнение | ~1 | да | правая |
Сдвиги
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
<< | сдвиг влево, добавление |
1 << 2, STDOUT << "foo"
| да | левая |
>> | сдвиг вправо | 1 >> 2 | да | левая |
Побитовые
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
& | побитовое И | 1 & 2 | да | левая |
| | побитовое ИЛИ | 1 | 2 | да | левая |
^ | побитовое исключающее ИЛИ | 1 ^ 2 | да | левая |
Равенство и сравнение
Равенство
Равенство проверяют три базовых оператора:
-
==: проверяет, равны ли значения операндов -
=~: проверяет, соответствует ли значение первого операнда значению второго операнда с помощью сопоставления с образцом. -
===: проверяет, соответствует ли левый операнд правому операнду при сравнении с образцом case. Этот оператор применяется в условияхcase ... when.
У первых двух операторов также есть операторы инверсии (!= и !~), семантически предназначенные для выполнения обратного действия относительно базового оператора: предполагается, что a != b эквивалентно !(a == b), а a !~ b — !(a =~ b). Тем не менее эти инверсии можно определить с помощью собственной реализации. Это может быть полезно, например, для повышения производительности (неравенство зачастую можно доказать быстрее, чем равенство).
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
== | равно | 1 == 2 | да | левая |
!= | не равно | 1 != 2 | да | левая |
=~ | совпадение с образцом | "foo" =~ /fo/ | да | левая |
!~ | несовпадение с образцом | "foo" !~ /fo/ | да | левая |
=== | сравнение на равенство в case | /foo/ === "foo" | да | левая |
Сравнение
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
< | меньше | 1 < 2 | да | левая |
<= | меньше или равно | 1 <= 2 | да | левая |
> | больше | 1 > 2 | да | левая |
>= | больше или равно | 1 >= 2 | да | левая |
<=> | сравнение | 1 <=> 2 | да | левая |
Цепочки операторов равенства и сравнения
Операторы равенства и сравнения ==, !=, ===, <, >, <= и >= можно объединять в цепочки; они интерпретируются как составное выражение. Например, a <= b <= c рассматривается как a <= b && b <= c. Можно даже смешивать операторы с одинаковым приоритетом, например a >= b <= c > d. Также можно объединять в цепочки операторы с разным приоритетом, однако этого рекомендуется избегать, поскольку это затрудняет понимание кода. Например, a == b <= c интерпретируется как a == b && b <= c, а a <= b == c — как a <= (b == c).
Логические
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
&& | логическое И | true && false | нет | левая |
|| | логическое ИЛИ | true || false | нет | левая |
Диапазоны
Операторы диапазона используются в литералах диапазонов.
| Оператор | Описание | Пример | Можно переопределить |
|---|---|---|---|
.. | диапазон | 1..10 | нет |
... | исключающий диапазон | 1...10 | нет |
Распаковка
Операторы распаковки можно использовать только для деструктуризации кортежей в аргументах метода. Подробнее см. в разделе Распаковка и кортежи.
| Оператор | Описание | Пример | Можно переопределить |
|---|---|---|---|
* | распаковка | *foo | нет |
** | двойная распаковка | **foo | нет |
Условный оператор
Компилятор преобразует условный оператор (? :) во внутреннее выражение if.
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
? : | условный оператор | a == b ? c : d | нет | правая |
Присваивания
Оператор присваивания = присваивает первому операнду значение второго. Первый операнд — это либо переменная (в этом случае оператор нельзя переопределить), либо вызов (в этом случае оператор можно переопределить). Подробнее см. в разделе Присваивание.
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
= | присваивание переменной | a = 1 | нет | правая |
= | присваивание вызову | a.b = 1 | да | правая |
[]= | присваивание по индексу | a[0] = 1 | да | правая |
Составные операторы присваивания
Оператор присваивания = служит основой для всех операторов, объединяющих оператор с присваиванием. Общая форма — a <op>= b, и компилятор преобразует её в a = a <op> b.
Логические операторы являются исключениями из общего правила преобразования:
-
a ||= bпреобразуется вa || (a = b) -
a &&= bпреобразуется вa && (a = b)
Есть ещё один особый случай: если a — это оператор индексации ([]), он заменяется вариантом, допускающим nil ([]? в правой части:
-
a[i] ||= bпреобразуется вa[i] = (a[i]? || b) -
a[i] &&= bпреобразуется вa[i] = (a[i]? && b)
Все преобразования предполагают, что получатель (a) является переменной. Если это вызов, замены семантически эквивалентны, но их реализация немного сложнее (создаётся анонимная временная переменная), и предполагается, что a= можно вызвать.
Получателем может быть только переменная или вызов.
| Оператор | Описание | Пример | Можно переопределить | Ассоциативность |
|---|---|---|---|---|
+= | сложение и присваивание | i += 1 | нет | правая |
&+= | циклическое сложение и присваивание | i &+= 1 | нет | правая |
-= | вычитание и присваивание | i -= 1 | нет | правая |
&-= | циклическое вычитание и присваивание | i &-= 1 | нет | правая |
*= | умножение и присваивание | i *= 1 | нет | правая |
&*= | циклическое умножение и присваивание | i &*= 1 | нет | правая |
/= | деление и присваивание | i /= 1 | нет | правая |
//= | деление с округлением вниз и присваивание | i //= 1 | нет | правая |
%= | остаток от деления и присваивание | i %= 1 | да | правая |
|= | побитовое ИЛИ и присваивание | i |= 1 | нет | правая |
&= | побитовое И и присваивание | i &= 1 | нет | правая |
^= | побитовое исключающее ИЛИ и присваивание | i ^= 1 | нет | правая |
**= | возведение в степень и присваивание | i **= 1 | нет | правая |
<<= | сдвиг влево и присваивание | i <<= 1 | нет | правая |
>>= | сдвиг вправо и присваивание | i >>= 1 | нет | правая |
||= | логическое ИЛИ и присваивание | i ||= true | нет | правая |
&&= | логическое И и присваивание | i &&= true | нет | правая |
Операторы индексации
Операторы индексации используются для получения значения по индексу или ключу, например элемента массива или записи в отображении. Вариант, допускающий nil, []?, должен возвращать nil, если индекс не найден, тогда как вариант, не допускающий nil, в таком случае вызывает исключение. Реализации в стандартной библиотеке обычно вызывают KeyError или IndexError.
| Оператор | Описание | Пример | Можно переопределить |
|---|---|---|---|
[] | оператор индексации | ary[i] | да |
[]? | оператор индексации, допускающий nil | ary[i]? | да |
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/1.2/syntax_and_semantics/operators.html