Spec-Zone.ru › Crystal

Операторы

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API