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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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 Vectors 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:

Swift custom operators precedence and associativity table

Here are a few notes about these operators, since you may not have seen them before:

  1. Bitwise shift operators, << and >>, are used for binary calculations.
  2. You use casting operators, is and as, to determine or change a value's type.
  3. The nil coalescing operator, ??, helps providing a fallback value for optional values.
  4. If your custom operator does not specify a precedence, DefaultPrecedence is automatically assigned.
  5. The ternary operator, ? :, is analogous to an if-else statement.
  6. 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.

Owen L Brown

Contributors

Owen L Brown

Author

Evan Dekhayser

Author

Bas Broek

Tech Editor

Adriana Kutenko

Illustrator

Shai Mishali

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.