You have been spared the terrible pun for this title, I knew the last one was trouble when it was typed in.
So, in the earlier article Operator Overloading — Tailor Swift To Your Needs, you saw how you could add functionality to already existing operators to extend their functionality to work with new classes, or in that post’s case, existing ones that it might make sense with. Now, what if you want a completely new operator? What if you have functionality so unique, that no current operator makes sense? That’s where custom operators comes in. You can declare your own operator and have it run whatever code you want. Now, like last time, this is a very powerful, but very dangerous capability. You COULD have it play sounds, make network calls, draw something to screen, or any number of annoying things, but you really should not have those in your operator overloading or custom operator methods. They are meant to make things more readable, not less. Unless you use some emoji for a loudspeaker in your operators, you really shouldn’t have it play sounds, and even if you do, why not just use a method?
Also of course, since these are meant to increase readability, you have to be careful about using these. For instance, can you tell me what the ” #$^&* ” operator does? Obviously somebody new to a codebase needs some time to get up to speed with it, and even normal functions will take some time to learn the intricacies of. However, if you compress a gigantic algorithm into a custom operator, that may take longer to figure out than a well named method doing the same.
Nonetheless though, if you use custom operators sparingly, well document them, and don’t give them side effects (like making network calls, playing sounds, etc), they can very useful for increasing legibility and decreasing the character count in your code.
Types of Custom Operators
In Swift, there can be operators that operate on 1, 2, or 3 values. We call those operators unary, binary, or ternary (respectively).
Swift has 4 types of operators, 3 of which are available for us to use with custom operators they are:
Keyword | Description |
prefix | A unary (operating on one value) operator that is just before the value it operates on. |
postfix | A unary operator that is just after the value it operates on. |
infix | A binary (operating on two values) operator placed between the two values it operates on. |
There is a fourth style of operator in Swift known as the ternary operator. It is not included in the above table though, because currently we cannot write our own ternary operator functions. The Swift language itself has only one ternary function, a conditional operator. It was mentioned as how the nil-coalescing operator works under the hood in the previous article Nil Coalescing in Swift.
You use these keywords when declaring your operator, letting Swift know that your combination of symbols is an operator, and how it will be used with the value(s) it uses or modifies.
Implementing A Postfix or Prefix Operator
I was never the biggest fan of using the ” % ” key on a calculator, I always preferred just typing the percentage as a decimal. But, presumably people did use it, so that seemed like a great example of a postfix operator. The basic idea will be for it to be a postfix operator that will take in an Integer and return a Double that is the decimal version of that percentage, such that 42% == 0.42.
It could take a Double as an input too, but it will be the same code, just with a function overload to take the Double instead of the Integer. Also, unlike other postfix operators (like ++ or –), this one will not modify the value that is passed in.
Since the code to perform this is so small, I will just show it all below and explain it afterwards:
postfix operator % {} postfix func % (percentage: Int) -> Double { return (Double(percentage) / 100) }
Now, the astute among you may remember that there actually is a ” % ” operator in Swift. In Swift, the ” % ” operator is a remainder operator. It is used like the division operator, but instead of returning the quotient of the division operation, it returns the remainder. However though, that operator, since it operates on two values is an infix operator. There are no ” % ” postfix operators built into Swift, so we had to declare it as a new one.
So, for the first line, we declared that it is an operator, that it is a postfix one (so placed immediately after value it uses), and that its symbol is ” % “. For unary operator (prefix or postfix), those brackets must be there, but they should be empty. We will see what they are used for when we discuss infix operators.
Then, similar to operator overloading, we create a function for our new operator. We specify that it is still a postfix operator, and then follow it up with the rest of the normal way of writing a function. As said earlier, this operator will operate on an Integer, and return a Double. We then just write a normal function. First the “percentage” Integer is cast to a Double, and then that new Double is divided by 100. The result of this calculation is returned by the function.
If you wanted to also take a Double as the input, all you would have to do is change the “percentage” parameter type to Double, and remove that cast to Double in the function.
So with the above code, the code below will now be valid in the same module:
let fivePercent = 5% // fivePercent is now a Double that holds the value 0.05 let fortyTwoPercent = 42% // fivePercent is now a Double that holds the value 0.42
To implement a prefix operator, you really just do the same thing as for the postfix, but just with the “prefix” operator instead of “postfix”. It doesn’t look as pretty, but just for the sake of having an example, here is the % changed into a prefix operator (same code otherwise):
prefix operator % prefix func % (percentage: Int) -> Double { return (Double(percentage) / 100) } //Example Use let fortyTwoPercent = %42 // fivePercent is now a Double that holds the value 0.42
I definitely prefer it as a postfix operator, but this just shows you how similar writing a prefix and postfix operator are.
Implementing an Infix Operator
These operators are the kind that operate on two values, placed between them. The most notable examples are that standard arithmetic operators like +, -, *, and /. So when you add, you use it like in the expression ” a + b “, placing it between them.
Currently in Swift there is a ” ^ ” infix operator. It is used for the bitwise exclusive OR function. When used in a mathematic context though, it is often used to refer to raising something to a power, like 5^2 would mean “Five squared” or “Five raised to the power of two”. I don’t want to use the ” ^ ” operator since that is already used for the XOR function, so let’s use an operator based on it, but different enough to not be confused, like ” ^^^ “. With 3 ^s in a row, it is much less likely to mistake it than if we had only 2.
Below is the code for implementing the ” ^^^ ” operator for raising a number to a power:
precedencegroup ExponentialPrecedence { lowerThan: BitwiseShiftPrecedence higherThan: MultiplicationPrecedence associativity: right } infix operator ^^^: ExponentialPrecedence func ^^^ (base: Int, power: Int) -> Double { return pow(Double(base), Double(power)) }
For infix operators, you have to specify the associativity and the precedence for an operator. So what are those values in the precedencegroup? It might be easier to show this one by example.
The division operator is left associative, as such, the expression 7.5 / 4.0 / 2.5 = 0.75.
If we used parenthesis to show what it is actually doing, the correct expression would be:
- (7.5 / 4.0) / 2.5.
- (1.875) / 2.5
- 0.75
If we decided to make it right-associative (which is wrong for division, but showing how it changes things) we would get the expression:
- 7.5 / (4.0 / 2.5)
- 7.5 / (1.6)
- 4.6875
Very different. The exponentiation operator is right associative, so 5^4^2 is equivalent to 5^(4^2). This is why we should use right associativity. You have the choice of right, none, or left. You can read more about operator associativity in this Wikipedia article.
The next value is precedence. This determines for values with the same associativity, which ones should be evaluated first. In this case, I defaulted to good ol’ PEMDAS. On the Operator Declarations page of Apple’s developer site, you can see the associativity and precedence for operators in Swift. Parenthesis and exponents have higher precedence than Multiplication, so in the ExponentialPrecedence group we defined, we set the precedence higher than the MultiplicationPrecedence that is built into Swift. Bitwise Shift operations have the highest precedence, and since this is working with numbers instead of bits, it seems logical to have an exponential operator be lower than the BitwiseShiftPrecedence. This might be wrong, but this is just an example of how to make custom operators, not math class.
Nonetheless, once those are set, we assign that precedencegroup to our new ^^^ operator, and after that, we actually write the function itself. One thing to note is that you don’t have to write “infix” before your operator function, unlike how we had to write which kind for a prefix or postfix operator. You do still have to write it when you declare the operator itself, just not the function that makes the operator DO something. If you try to use it, you get an error.
If you do not specify the precedence and associativity, the default precedence is 100 and the default associativity is none. This puts them right above assignment in precedence (as in saying assigning a value to a variable).
So now, with this operator, these expressions are now valid in your Swift module:
let fiveSquared = 5 ^^^ 2 // fiveSquared is now a Double that holds the value 25.0 let powerOfZero = 123456 ^^^ 0 // powerOfZero is now a Double that holds the value 1.0 let negativeExponent = 10 ^^^ -2 // negativeExponent is now a Double that holds the value 0.01
Conclusion
As with its sibling, Operator Overloading, a certain cliché comes to mind. I will spare you from hearing it another time, but there is tremendous responsibility in using this capability for good. Custom operators can make code nigh unreadable if you are not careful, so do your best to make sure you choose a good set of symbols for your operator, and to document it very well. It is also probably best not to make too many custom operators in the same project. I’m not sure when I’ll use custom operators myself, but I am glad for the capability to do it when I DO find a need.
One more thing to note, all operator declarations and functions must be placed at global scope, outside of any type (class, structure, or enumeration).
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!