Overloading Custom Operators in Swift
In this Swift tutorial, you’ll learn how to create custom operators, overload existing operators and set operator precedence. By Owen L Brown.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Overloading Custom Operators in Swift
20 mins
Mixed Parameters? No Problem!
You can also multiply vectors by a number through scalar multiplication. To multiply a vector by two, you multiply each component by two. You’re going to implement this next.
One thing you need to consider is the order of the arguments. When you implemented addition, order didn’t matter because both parameters were vectors.
For scalar multiplication, you need to account for Int * Vector
and Vector * Int
. If you only implement one of these cases, the Swift compiler will not automatically know that you want it to work in the other order.
To implement scalar multiplication, add the following two functions below the subtraction function you’ve just added:
static func * (left: Int, right: Vector) -> Vector {
return [
right.x * left,
right.y * left,
right.z * left
]
}
static func * (left: Vector, right: Int) -> Vector {
return right * left
}
To avoid writing the same code multiple times, your second function simply relays its arguments to the first one.
In mathematics, vectors have another interesting operation known as the cross-product. How cross-products work is beyond the scope of this tutorial, but you can learn more about them on the Cross product Wikipedia page.
Since using custom symbols is discouraged in most cases (who wants to open the Emoji menu while coding?), it would be very convenient to reuse the asterisk for cross-product operations.
Cross-products, unlike scalar multiplication, take two vectors as arguments and return a new vector.
Add the following code to add the cross-product implementation after the multiplication function you’ve just added:
static func * (left: Vector, right: Vector) -> Vector {
return [
left.y * right.z - left.z * right.y,
left.z * right.x - left.x * right.z,
left.x * right.y - left.y * right.x
]
}
Now, add the following calculation to the bottom of your playground, leveraging both your multiplication and cross-product operators:
vectorA * 2 * vectorB // (-14, -10, 22)
This code finds the scalar multiple of vectorA
and 2, then finds the cross-product of that vector with vectorB
. Note that the asterisk operator always goes from left to right, so the previous code is the same as if you had used parentheses to group the operations, like (vectorA * 2) * vectorB
.
Protocol Operators
Some operators are required members of protocols. For example, a type that conforms to Equatable
must implement the ==
operator. Similarly, a type that conforms to Comparable
must implement at least <
and ==
, because Comparable
inherits from Equatable
. Comparable
types may also optionally implement >
, >=
, and <=
, but these operators have default implementations.
For Vector
, Comparable
doesn't really make a lot of sense, but Equatable
does, since two vectors are equal if their components are all equal. You’ll implement Equatable
next.
To conform to the protocol, add the following code at the end of your playground:
extension Vector: Equatable {
static func == (left: Vector, right: Vector) -> Bool {
return left.x == right.x && left.y == right.y && left.z == right.z
}
}
Add the following line to the bottom of your playground to test this out:
vectorA == vectorB // false
This line returns false
as expected, because vectorA
has different components than vectorB
.
Conforming to Equatable
gives you more than the ability to check for equality of these types. You also gain access to contains(_:)
for an Array
of Vector
s for free!
Creating Custom Operators
Remember how I said that using custom symbols is usually discouraged? As always, there are exceptions to the rule.
A good rule of thumb about custom symbols is that you should only use them if the following are true:
- Their meanings are well-known or would make sense to someone reading the code.
- They are easy to type on the keyboard.
This last operator you will implement matches both of these conditions. The vector dot-product takes two vectors and returns a single scalar number. Your operator will multiply each value in a vector by its counterpart in the other vector, then add up all these products.
The symbol for dot-product is •
, which you can easily type using Option-8 on your keyboard.
You might be thinking, "I can just do the same thing I did with every other operator in this tutorial, right?"
Unfortunately, you can't do that just yet. In the other cases, you are overloading an operator that already exists. For new custom operators, you need to create the operator first.
Directly underneath the Vector
implementation, but above the CustomStringConvertible
conformance extension, add the following declaration:
infix operator •: AdditionPrecedence
This defines •
as an operator that must be placed between two other values and has the same precedence as the addition operator +
. Ignore precedence just for the moment because you'll come back to it.
Now that this operator has been registered, add its implementation at the end of your operators extension, immediately below the implementation of the multiplication and cross-product operators *
:
static func • (left: Vector, right: Vector) -> Int {
return left.x * right.x + left.y * right.y + left.z * right.z
}
Add the following code to the bottom of your playground to test this out:
vectorA • vectorB // 15
Everything looks good so far...or does it? Try the following code at the bottom of the playground:
vectorA • vectorB + vectorA // Error!
Xcode isn't very happy with you. But why?
Right now, •
and +
have the same precedence, so the compiler parses the expression from left to right. The compiler interprets your code as:
(vectorA • vectorB) + vectorA
This expression boils down to Int + Vector
, which you haven’t implemented and don't plan to implement. What can you do to fix this?
Precedence Groups
All operators in Swift belong to a precedence group, which describes the order in which operators should be evaluated. Remember learning the order of operations in elementary school math? That is essentially what you're dealing with here.
In the Swift standard library, the order of precedence is as follows:
Here are a few notes about these operators, since you may not have seen them before:
- Bitwise shift operators,
<<
and>>
, are used for binary calculations. - You use casting operators,
is
andas
, to determine or change a value's type. - The nil coalescing operator,
??
, helps providing a fallback value for optional values. - If your custom operator does not specify a precedence,
DefaultPrecedence
is automatically assigned. - The ternary operator,
? :
, is analogous to an if-else statement. -
AssignmentPrecedence
, for the derivatives of=
, is evaluated after everything else, no matter what.
The compiler parses types that have a left associativity so that v1 + v2 + v3 == (v1 + v2) + v3
. The opposite is true for right associativity.
Operators are parsed in the order they appear in the table. Try to rewrite the following code using parentheses:
v1 + v2 * v3 / v4 * v5 == v6 - v7 / v8
When you're ready to check your math, look at the solution below.
[spoiler title="Solution"]
(v1 + (((v2 * v3) / v4) * v5)) == (v6 - (v7 / v8))
[/spoiler]
In most cases, you'll want to add parentheses to make your code easier to read. Either way, it's useful to understand the order in which the compiler evaluates operators.