Operator overloading is one of the most powerful features of any computer programming language, so Apple decided to make it available for development in Swift. However, with great power comes great responsibility. You can easily implement really weird scenarios with it, such as making the subtraction operator add numbers or the division one multiply them, but that is definitely not what you want to do with it.
OK, enough talking – let’s see what operator overloading is all about.
The Challenge
Your task for this tutorial is an easy one: extend the multiplication operator’s standard functionality for numbers so that it works for strings as well. You are going to use the string concatenation operator under the hood, since you can imagine the whole thing like this:
"abc" * 5 = "abc" + "abc" + "abc" + "abc" + "abc" = "abcabcabcabcabc"
Before diving into coding, think how you would solve the problem and break it into steps. This is how I would do it:
- Create the result variable and assign its initial value – the default string.
- Loop through the numbers from 2 to the number of concatenations and do only one thing for each iteration – add the string to the result.
- Print the result to the console.
That’s it for the algorithm – let’s move on to the implementation.
Basic Operator Overloading
Fire up Xcode and open a playground. Delete everything from it and add the multiplication operator function’s prototype:
func *(lhs: String, rhs: Int) -> String {
}
The function has two parameters – the left hand side operand of type String
and the right hand side operand of type Int
– and returns the multiplication result as String
.
There are three things you should do inside the function’s body. First of all, create the result variable and assign its initial value – the function’s String
argument – it is variable because you are going to change its value soon:
var result = lhs
Next loop through the numbers from 2 to the function’s Int
argument with the for in
control flow statement and the closed range operator:
for _ in 2...rhs {
}
Note: You implement the wildcard pattern matching with the underscore since you ignore the sequence values – read more about loops here.
There is only one thing you should do in the loop – update the result with the string value:
result += lhs
Note: You could also do it like this – the initial approach is shorter because it uses the addition assignment operator:
result = result + lhs
Finally, return the result:
return result
Now let’s use the operator:
let u = "abc"
let v = u * 5
That’s it! There is only one problem – you can only use the operator to multiply strings. How about other value types? Let’s fix this with generic operators.
Generic Operators
Generic types don’t work with operators by default, so you need a protocol for that. Add its prototype to the playground:
protocol Type {
}
Now add the addition assignment operator function’s prototype to the protocol:
func +=(inout lhs: Self, rhs: Self)
The function has both the left hand side and right hand side operands of type Self
– this is just a fancy way of saying that they are of any type which implements the protocol. The left hand side operand is marked as inout, since its value is modified and returned from the function.
Alternatively, you can define the addition operator function’s prototype:
func +(lhs: Self, rhs: Self) -> Self
The function has both the left hand side and right hand side operands of type Self
and returns the addition result as a value of type Self
. In this case, you do not need to use inout
parameters anymore.
Next, create extensions for the String
, Int
, Double
and Float
types that implement the Type
protocol:
extension String: Type {}
extension Int: Type {}
extension Double: Type {}
extension Float: Type {}
Note: The implementation of the extensions are empty because you don’t want to add anything to the default types. You simply make them in order to conform to the protocol.
Now add the multiplication operator function’s prototype to the playground file:
func *(lhs: T, rhs: Int) -> T {
}
The function takes in two parameters – the left hand side operand of type T
and the right hand side operand of type Int
– and returns the multiplication result as a value of type T
. You use a type constraint to make the generic type T
conform to the Type
protocol, so it now understands the addition assignment operator.
Note: You can define the type constraint with the where keyword instead – the initial approach is shorter though:
func *(lhs: T, rhs: Int) -> T
The function’s implementation is exactly the same as in the previous case:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
Note: You can use the addition operator instead – make sure to add its function prototype to the protocol in this case.
Now let’s see the generic operator in action:
let x = "abc"
let y = x * 5
let a = 2
let b = a * 5
let c = 3.14
let d = c * 5
let e: Float = 4.56
let f = e * 5
That’s it! There is only one problem: you are using the standard multiplication operator. That’s a bit confusing. It would better if we can change it to other operators. Okay, let’s see how we can fix this with custom operators.
Custom operators
Add this line to the playground file to get started:
infix operator ** {associativity left precedence 150}
Here’s what’s going on here, step-by-step:
- The custom multiplication operator’s name is `**`.
- Its type is `infix` since it is a binary operator with two operands.
- It evaluates from left to right, so it has a left associativity.
- Its precedence is `150` – the same as the standard multiplication operator’s one, since it is a high priority operator.
Note: You can read more about operator precedence and associativity here.
The custom operator function’s prototype is similar to the standard one – just its name is different:
func **(lhs: T, rhs: Int) -> T {
}
The function’s implementation is exactly the same as before:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
Here’s the custom operator in action:
let g = "abc"
let h = g ** 5
let i = 2
let j = i ** 5
let k = 3.14
let l = k ** 5
let m: Float = 4.56
let n = m ** 5
That’s it! There is only one problem – the operator’s compound version is not defined yet, so let’s fix this in the next section.
Compound Operators
The compound operator’s type, precedence and associativity is exactly the same as in the previous case – only its name is different:
infix operator **= {associativity left precedence 150}
Next add the compound operator function’s prototype to the playground:
func **=(inout lhs: T, rhs: Int) {
}
The function doesn’t have a return type, since its left hand side operand is marked as inout
.
There is only one thing you should do in the function’s body – use the custom operator defined earlier to return the multiplication result:
lhs = lhs ** rhs
Now let’s use the operator:
var o = "abc"
o **= 5
var q = 2
q **= 5
var s = 3.14
s **= 5
var w: Float = 4.56
w **= 5
That’s it – it can’t get any simpler than that!
Conclusion
Operator overloading, used with caution, can be extremely powerful – I hope you find a way to use it in your own projects.
For reference, you can download the Playground file on GitHub. I have tested the code on Xcode 7.3 and Swift 2.2.
What do you think about this tutorial and operator overloading? Please leave me comment and share your thought.