Swift традиционно уделяет большое внимание чистоте синтаксиса и позволяет создавать собственные операторы (operator overloading), включая новые символы и даже ключевые слова. Это расширяет возможности DSL и позволяет делать код очень выразительным.
Без понимания принципов объявления операторов можно получить неоднозначный, плохо читаемый и трудно поддерживаемый код. Неправильно выбранная приоритетность или ассоциативность приведет к неожиданным результатам. Компилятор позволяет создавать весьма «опасные» выражения, если не указывать ограничения.
Можно объявлять новые infix, prefix, postfix операторы, указывать их приоритеты и области применения. Пример:
infix operator ~> : AdditionPrecedence func ~> (lhs: Int, rhs: Int) -> Int { return lhs * 10 + rhs } let x = 2 ~> 3 // 23
Для custom приоритетов нужно объявить:
precedencegroup MyPrecedence { associativity: left higherThan: AdditionPrecedence } infix operator *** : MyPrecedence
Ключевые особенности:
Обязательно ли реализовывать и функцию, и объявление оператора?
Да, если объявили оператор, нужно реализовать соответствующую функцию — иначе будет ошибка компилятора. Функции имеют подпись, совпадающую с оператором по сигнатуре.
Как правильно выбрать precedencegroup и как влияют group на порядок вычислений?
precedencegroup задаёт приоритет вычисления и ассоциативность для infix-операторов. Неправильный выбор group может привести к неожиданным результатам в выражениях с несколькими операторами (например, умножение/сложение и ваш custom-оператор).
Можно ли объявить custom оператор с текстовым именем?
Нет, custom операторы доступны только через специальные символы или последовательности, определённые синтаксисом Swift. Текстовые стандартные имена не разрешены для объявления как оператор.
В проекте были объявлены операторы <<< и >>> для странных преобразований коллекций, без описания приоритета, ассоциативности и документации. Новые сотрудники не понимали, что они делали и в каком порядке вычислялись выражения.
Плюсы:
Минусы:
В проекте использовали custom-оператор => для декларативной сборки chainable pipeline (например, в UI билдере), с чётко описанным приоритетом и документированной реализацией. Каждый разработчик понимал, что он делает и как используется.
Плюсы:
Минусы: