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,
- Float
- Double
- Int
- Binary
- Octal
- Hexadecimal
Please note however that we still want to keep power asInt
since that is a given requirement. You can still change the base to anyNumeric
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!