Operator Overloading in Swift Tutorial

Learn how to extend operators for new types or create entirely new operators in this new Swift tutorial! By Corinne Krych.

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

Defining Custom Operators

There are three steps to define a custom operator:

  1. Name your operator
  2. Choose a type
  3. Assign precedence and associativity

Let’s go over these one by one.

Naming Your Operator

Now, you have to pick a character for your operator. Custom operators can begin with one of the ASCII characters /, =, -, +, !, *, %, <, >, &, |, ^, or ~, or with one of the Unicode characters. This gives you a broad range of possible characters. Don’t go crazy, though—remember that you’ll have to type these characters repeatedly and the fewer keystrokes, the better.

In this case, you’ll copy and paste the Unicode symbol , a representation of direct sum that fits your purposes nicely.

Choosing a Type

With Swift, you can define binary, unary and ternary operators. These indicate the number of targets involved.

  • Unary operators involve a single target and are defined either as postfix (i++) or prefix (++i), depending on where they appear in relation to the target.
  • Binary operators are infix because they appear in between the two targets, for example 1 + 1.
  • Ternary operators operate on three targets. In Swift, the conditional operator is the only Ternary operator, for example a ? b : c.

You should choose between these based on the number of targets of your operation. You want to add two arrays (i.e. 2 targets), so you will define a binary operator.

Assigning Precedence and Associativity

Because operators are defined globally, you need to choose the associativity and precedence of your custom operator with care.

This can be tricky, so a good rule of thumb is to find a comparable standard operator defined in the Swift language reference and use similar semantics. For example, to define vector addition, it makes sense to use the same associativity and precedence as the + operator.

Coding Your Custom Operator

Back in your playground, enter the following to define your custom operator. You’ll probably want to copy and paste the ⊕ for simplicity.

infix operator ⊕ { associativity left precedence 140 } // 1
func ⊕(left: [Int], right: [Int]) -> [Int] { // 2
    var sum = [Int](count: left.count, repeatedValue: 0)
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        sum[key] = left[key] + right[key]
    }
    return sum
}

This code is similar to your earlier override, aside from section 1, where you do all of the following:

  • Define an infix/binary operator that operates on two targets and is placed in between those targets.
  • Name the operator ⊕.
  • Set associativity to left, indicating that operands of equal precedence will use left associativity to determine order of operation.
  • Set the precedence to 140, which is the same weight as Int addition, as found in the Swift language reference.

The code in section 2 is similar to what you’ve seen so far – it adds the two arrays item by item, based on their order in the arrays. Test the new operator by adding the following to your playground:

var sumArray = [1, 2, 3] ⊕ [1, 2, 3]

You’ll see the same results as the earlier function and override, but this time, you have different operators for different semantics.

Bonus Round!

Now that you have a good idea of how to create a custom operator, it’s time to challenge yourself. You’ve already created a ⊕ operator to perform vector addition, so use that knowledge to create a ⊖ operator for subtracting Int arrays in a similar manner. Give it your best shot, and check your solution against the answer below.

[spoiler title=”Solution”]

infix operator  ⊖ { associativity left precedence 140 }
func ⊖(left: [Int], right: [Int]) -> [Int] {
  var minus = [Int](count: left.count, repeatedValue: 0)
  assert(left.count == right.count, "vector of same length only")
  for (key, v) in enumerate(left) {
    minus[key] = left[key] - right[key]
  }
  return minus
}

And for testing:

var subtractionArray = [1, 2, 3] ⊖ [1, 2, 3]

[/spoiler]

Remember Related Operators!

If you define a new operator, don’t forget to define any related operators.

For example, the addition assignment operator (+=) combines addition and assignment into a single operation. Because your new operator is semantically the same as addition, it’s a good idea to define the assignment operator for it, as well.

Add the following to Operator2.playground:

infix operator  ⊕= { associativity left precedence 140 } // 1
func ⊕=(inout left: [Int], right: [Int]) { // 2
    left = left ⊕ right
}

Line 1 is exactly the same declaration as for the ⊕ operator, except it uses the compound operator symbol.

The trick is on line 2, where you mark the compound assignment operator’s left input parameter as inout, which means the parameter’s value will be modified directly from within the operator function. As a result, the operator doesn’t return a value—it simply modifies the input.

Test that this operator works as expected by adding the following to your playground:

sumArray ⊕= [1, 1, 1]

You’ll see the following in the console:

[3, 5, 7]

As you can see, defining your own operators is not difficult at all! :]

Defining Operators for More Than One Type

Now imagine that you want to define vector addition for decimal points, too.

One way is to overload a new operator for the types Double and Float, just like you did for Int.

It’s just a couple of lines of code, but you’ll have to use copy/paste. If you’re like me—a clean code addict—then duplicating code is not your first choice, as it makes your code harder to maintain.

Generics to the Rescue!

Luckily, Swift generics can help with exactly this! If you need a refresher on Swift generics, check out our Swift Generics Tutorial released earlier this week.

Start a new playground so you have a clean slate. Add the following to your new playground:

infix operator ⊕ { associativity left precedence 140 }
func ⊕<T>(left: [T], right: [T]) -> [T] { // 1
    var minus = [T]()
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        minus.append(left[key] + right[key]) // 2
    }
    return minus
}

On line 1, you define a generic function called ⊕ that works with a placeholder type signified by T. The playground isn’t happy. You’ll see a compile error that looks like this: Could not find an overload for '+' that accepts the supplied arguments.

The error comes from line 2, where you’re attempting to use the + operator on the values left and right, which are the placeholder type. Swift doesn’t know how to apply a + operand to those variables, because it has no idea what type they are!

Corinne Krych

Contributors

Corinne Krych

Author

Over 300 content creators. Join our team.