Creating Custom Operators in Swift with Generics

Creating Custom Operators in Swift with Generics

Swift introduced us to a new era of custom operators. Unlike Objective-C, it allows us to define our own operators and perform operator overloading on existing operators if necessary. We will see both of them in detail in today's article.


Custom operators can provide excellent clarity and flexibility down the line. They add extra context to code and can also reduce unnecessary lines when everything can be put in one location to perform a single task. Let's start with how you can define the custom operator in your application.

Defining Custom Operator

There are three ways any operator can be used,

Prefix Operator

The prefix operator is attached to the beginning of the operand. Such as [operator][operand]. Also regarded as unary operator since it requires only one operand to work on. For example ~x or ++x

Postfix Operator

The postfix operator is attached to the end of the operand. Such as [operand][operator]. Also regarded as a unary operator since it requires only one operand to work on. For example, x++

Infix Operator

The infix operator is attached between two operands. Also called a binary operator since it operates on two operands. e.g. [operand1][operator][operand2]. For example, a + b or a b.  

Let's dive  deep into each of them to see how you can create your own custom operator based on the need,

Prefix Operator

To get started with custom operators, first, you have to decide which special symbol you want to use for the operator. Let's choose a special character combination ++++. This operator will increment the value of the operand by 4


// Operator definition
// Prefix operator
prefix operator ++++

prefix func ++++ (value: Int) -> Int {
    return value + 4
}

// Example
let value = 10
let result = ++++value

// prints 14 
print(result)

Postfix Operator

Similarly, we can define our postfix operator. The only difference being instead of prefixing the operator we will postfix it to the operand.


postfix operator ++++

postfix func ++++ (value: Int) -> Int {
    return value + 4
}

// Example
let value = 10
let result = value++++

// prints 14 
print(result)

Infix Operator

This is an interesting part. Being an infix operator involves more than one operand. We will also specify extra options such as precedence and associativity.

What is the Operator Precedence?

Precedence refers to the order in which arithmetic operations are carried out. The precedence of an operator specifies how tightly the operator binds to its operands, in the absence of grouping parentheses. In this expression,

2 + 5 * 4

The multiplication operator has higher precedence over the addition operator, so the multiplication operator tightly binds to its operator than the addition operator, and thus multiply operation happens first before addition.

The precedence operator determines the order in which expressions are evaluated where operations associated with higher precedence operators are always evaluated first

What is the operator Associativity?

Associativity of operators refers to how a set of operators with the same precedence level are grouped together in the absence of parentheses. In other words, associativity controls the direction in which expression is evaluated when involved operators have the same precedence level. Associativity can be left, right, or none. Meaning, expressions involving operators with the same precedence are evaluated either from left, right, or result in an error.

Let's look at the example,

let output = 4 - 6 - 5

What do you think the value of the expression is?

Since subtraction operator - is left-associative, this expression becomes (4 - 6) - 5 and the answer is -7. Had this operator been right-associative, the expression would've become 4 - (6 - 5) and answer would've been 3

Another exercise,

What's the output of the following expression?

let output = 7 + 10 % 11 * 2

Since % and * operators have the same precedence, it takes associativity into consideration. Both the operators have left-associativity, so expression is evaluated from left

let output = 7 + (10 % 11) * 2 // Outputs: 27

Had it had right-associativity, the answer would've been

let output = 7 + 10 % (11 * 2) // Outputs 17

Can an operator be defined without associativity?

Yes. You can define the new operator with none associativity. If the conflict occurs about which direction to start evaluating expression from, the compiler will raise an error. For example, if the custom min value operator <<< had none associativity, the attempt to evaluate a <<< b <<< c will result in an error since the compiler doesn't know which direction to start resolving this expression.

Before when associativity was none

infix operator <<<: MinValueOperatorPrecedence

precedencegroup MinValueOperatorPrecedence {
    associativity: none
}

public func <<< (value1: Int, value2: Int) -> Int {
    return value1 < value2 ? value1 : value2
}

After when associativity was changed to left

infix operator <<<: MinValueOperatorPrecedence

precedencegroup MinValueOperatorPrecedence {
    associativity: left
}

public func <<< (value1: Int, value2: Int) -> Int {
    return value1 < value2 ? value1 : value2
}

// Outputs 50
let value1: Int = 300
let value2: Int = 50
let value3: Int = 100

let output = value1 <<< value2 <<< value3
The full list of iOS operator declarations can be found on the Operator Declarations page
You can find more information on Operator Declaration on this page

Defining a Custom Infix Operator

Now that we got the operator precedence and associativity concepts clear, let's see how we can define a custom infix operator in iOS using Swift.

Let's call our new operator ^^ which will compute the 4th power of a given number. For example, if 2^2 evaluates to 4, then 2^^2 will evaluate to 16.


// As per Swift 3.0 proposal you have to declare precedence group first before proceeding

// Here I have specified associativity right because I want this expression to be evaluated starting from right going to left. For example, if it's x ^^ y ^^ z, it will be regarded as x ^^ (y ^^ z). 

// Being the power operator, I am assigning it a precedence more than multiplication. So our double power operator will be evaluated first before applying multiplication or any other operator lower in precedence

precedencegroup SuperPowerPrecedence {
    associativity: right
    higherThan: MultiplicationPrecedence
}

infix operator ^^: SuperPowerPrecedence

// Operator definition
public func ^^ (value1: Double, value2: Double) -> Double {
    return pow(pow(value1, value2), value2)
}

let value = 10
let power = 2

// result = 10000
let result = value ^^ power


You can also define the operator with the in-place operation. Instead of applying it to the third variable, you can directly apply the result to the first variable. In short,
instead of having to do,


let result = value ^^ power

You can just do,

value ^^ power // value = 10000


public func ^^ ( value1: inout Double, value2: Double) {
    value1 = pow(pow(value1, value2), value2)
}

// value = 10000
value ^^ power

The way you may want to use the infix operator really depends on your specific use case. I know few use-cases where first definition is useful, but there may be few cases where second use cases will be useful for added readability

Bonus example with the infix operator

Let's look at one more example involving the infix operator. We will consider sets this time. Say, we want to add a custom operator to compute the intersection of two sets.

Swift already provides a built-in function to compute this.

public func intersection(_ other: Set) -> Set

All we need to do is to make it succinct by using our custom operator. Being a math student, my obvious choice would be to use this - A classic intersection operator.

infix operator ∩: SetOperatorPrecedence

precedencegroup SetOperatorPrecedence {
    associativity: left
    higherThan: MultiplicationPrecedence
}

public func ∩<T> (lhs: Set<T>, rhs: Set<T>) -> Set<T> {
    return lhs.intersection(rhs)
}

This should be it. So next time instead of doing,

let intersection = set1.intersection(set2)

You can write,

let intersection = set1 ∩ set2

let set1: Set = [1, 2, 4, 6]
let set2: Set = [1, 20, 3, 6]

// Result = [6, 1]
let result = set1 ∩ set2


Update - Defining Custom Operators with Generics

One of my colleagues mentioned that he came across this blog post and he loved it. I am delighted to hear it. However, I also realized that I don't have any example to demonstrate generics.

In this version, I am going to add support to demonstrate adding custom operators with generics

We will extend the code listed above with ^^ operator. We will make the first numeric parameter generic so that we can pass it as a number of any type (Double, Float, Int) and keep the second parameter as it is.

infix operator ^^

public func ^^ <T: Numeric>( value: inout T, power: Int) {
    var finalValue: T = 1
    for _ in 0..<power {
        finalValue = finalValue * value
    }
    value = finalValue
}

Now, given the above code, we can calculate the power of any Swift Numeric type including the following types,

  1. Float
  2. Double
  3. Int
  4. Binary
  5. Octal
  6. Hexadecimal
Please note however that we still want to keep power as Int since that is a given requirement. You can still change the base to any Numeric type given the constraint we imposed on custom type.
var floatValue: Float = 89.45
var doubleValue: Double = 10.45
var intValue: Int = 100
var binaryValue = 0b100
var octalValue = 0o11
var hexadecimalValue = 0xFA

let expectedPower = 3

// floatValue = 715716.438
floatValue ^^ expectedPower

// doubleValue = 1141.1661249999997
doubleValue ^^ expectedPower

// intValue = 1000000
intValue ^^ expectedPower

// binaryValue = 64
binaryValue ^^ expectedPower

// octalValue = 729
octalValue ^^ expectedPower

// hexadecimalValue = 15625000
hexadecimalValue ^^ expectedPower
This is all for today's post on creating custom operators in Swift. Hope this helps. With this post, now you know how to create Custom Operators in Swift with Generics. Thanks a lot for reading!

References:

Facets of Swift, Part 5: Custom Operators

Custom Operators in Swift

Precedence Group Declaration

Precedence and Associativity