Swift is a powerful and expressive language that provides a rich set of built-in operators. However, there are times when you need to create your own operators to make your code more readable, expressive, and concise, especially for domain-specific tasks. This article explores the process of creating custom operators in Swift with practical examples to help you understand and implement these concepts effectively.

Operators in Swift

Technically, operators are special symbols or phrases used to check, change, or combine values. Swift includes several types of operators:

  • Arithmetic Operators: Basic arithmetic operations like addition (+), subtraction (-), multiplication (*), division (/), and modulus (%).
  • Comparison Operators: Used to compare values, such as equality (==), inequality (!=), greater than (>), less than (<), and so on.
  • Logical Operators: Combine Boolean values, such as logical AND (&&), OR (||), NOT (!).
  • Bitwise Operators: Perform bit-level operations on binary numbers, such as AND (&), OR (|), XOR (^), and bitwise NOT (~).
  • Assignment Operators: Assign values to variables, with the basic form being the assignment (=) operator. Compound assignment operators like addition assignment (+=) are also included.
  • Range Operators: Create ranges, such as the closed range (…) and half-open range (..<)

Beside built-in operators, Swift also allows you to create your custom operators to manipulate data the fit your application’s unique requirements.

Creating a Custom Operator from Scratch

To better understand the process of creating a custom operator, let’s consider an example where we will write a function that takes two integers, adds them, and then squares the result.

Without a custom operator, you would write a normal function to achieve this:

func addAndSquare(left: Int, right: Int) -> Int {
    return (left + right) * (left + right)
}

// Usage
let result = addAndSquare(left: 3, right: 4)
print(result) // Output: 49

The above function works just fine. However, it would be nice if we define a custom operator to make it more concise and readable.

Generally, creating a custom operator in Swift involves two primary steps:

  • Define the Operator: Declare the operator and specify its precedence and associativity if needed.
  • Implement the Operator: Write the function that defines that the operator does.

Since this is a custom operator, you will have to pick the name yourself. For simplicity, I will use +* (Add And Square) for the operator’s name.

Here’s what the operator’s signature looks like:

infix operator +*

We simply use the operator keyword with the operator’s name. infix indicates that the operator is placed between its operand. Swift indeed allows you to define custom operators as prefix, postfix, or infix, depending on where you want the operator to appear relative to its operands.

  • prefix: Operators appear before their operands. For example, negation (-x) or incrementation (++number).
  • postfix: Operators appear after their operands. For example, post-incrementation (value++) or post-decrementation (value—).
  • infix: Operators appear between their operands. For example, standard arithmetic (+, -, *, /) or logical operations.

The next step is to implement the functionality of the operator:

func +*(left: Int, right: Int) -> Int {
    return (left + right) * (left + right)
}

This implementation is almost identical to the original one, except the function name is now the operator name.

Now you can use your custom operator:

let a = 3
let b = 4
let result = a +* b // (3 + 4) * (3 + 4) = 49
print(result) // Output: 49

We can go further by making the operator more generic so that it can work with multiple types. In our example, you might want the +* operator to be available for any numeric types. Here’s how we can improve it.

func +* <T: Numeric> (left: T, right: T) -> T {
    return (left + right) * (left + right)
}

Now we can use the +* operator with any type that conforms to the Numeric protocol.

let intResult = 3 +* 4
let doubleResult = 5.5 +* 2.5

print(intResult) // Output: 49
print(doubleResult) // Output: 64

Precedence and Associativity

The custom operator that we implemented above works fine when used in isolation. However, if you try to use it in an expression with other operators, there is an issue.

Consider the following expression:

let complexResult = 2 + 3 +* 4

This expression results in the following compiler error as Swift doesn’t know how to evaluate this expression correctly.

error: adjacent operators are in unordered precedence groups 'AdditionPrecedence' and 'DefaultPrecedence'

To understand this expression, Swift requires two crucial pieces of information: precedence and associativity.

  • Precedence determines the order in which operators are evaluated in expressions. Operators with higher precedence are evaluated before operators with lower precedence. This is similar to the order of operations in mathematics (e.g., multiplication before addition)
  • Associativity determines how operators of the same precedence are grouped in the absence of parentheses. It can be left, right, or none.

Without providing these details, the only way to ensure Swift can comprehend and execute your code correctly is to explicitly add parentheses to clarify the intended order of operations.

let complexResultLeft = (2 + 3) +* 4  // Output: 81
let complexResultRight = 2 + (3 +* 4) // Output: 51

To ensure custom operators work predictably with other operators, we need to define their precedence and associativity using a precedence group.

precedencegroup AddAndSquarePrecedence {
    associativity: right
    higherThan: MultiplicationPrecedence
}

infix operator +*: AddAndSquarePrecedence

Here, you’re creating a precedence group for the AddAndSquare operator, specifying right-associativity and higher precedence than multiplication. This allows Swift to understand expressions with this operator without needing parentheses.

let complexResult = 2 + 3 +* 4 // Output: 51

In conclusion, custom operators in Swift allow developers to create more expressive and readable code, tailored to specific domain requirements. The advantages include enhanced code clarity and reduced verbosity for complex operations. However, the disadvantages are potential misuse leading to obscure code and the added complexity of maintaining custom-defined behaviors, which may confuse other developers unfamiliar with the custom operators.

Thanks for reading! 🚀

Categorized in: