This is an argument that I like a lot, the possibility to create or to override an operator changing its functionality.
SImilar to C++ Operator Overloading (https://en.cppreference.com/w/cpp/language/operators) but in Swift.
Let’s see a simple example just to convince you to use it!
Percentage calculation
Why don’t you use the percentage sign “%” to calculate the percentage?
What do you think of this:
var fivePercent = 5% print( fivePercent ) // 0.05
If you like it, you can continue reading!
TL;DR
How works this percentage sign?
New operators are declared at a global level using the operator
keyword, and are marked with the prefix
, infix
or postfix
modifiers:
prefix[postfix, infix] operator %
This means that your new operator (%) can be used as:
postfix
like 5%prefix
like %5infix
like 2%5
as you prefer.
Example for percentage using POSTFIX:
postfix operator % postfix func % (percentage: Int) -> Double { return (Double(percentage) / 100) } var fivePercent = 5% print( fivePercent )
that’s all. You can use now 5% and get 0.05 as value.
Let’s see an example using PREFIX, the square root:
prefix operator √ prefix func √(lhs: Double) -> Double { return sqrt(lhs) } let someVal: Double = 25 let squareRoot = √someVal print( squareRoot )
Using operator prefix you can put your symbol before your number or your variable.
The last one is the INFIX operator that is used between numbers. Example:
infix operator • func •(lhs: Double, rhs: Double) -> Double { return lhs * lhs + rhs * rhs } let doubleVal1 = 2.0 let doubleVal2 = 3.0 let squareSum = doubleVal1 • doubleVal2 print( "squareSum: \(squareSum)" ) //squareSum: 13.0
this operator can be used between numbers.
Cool right?
Now, something more interesting about operators. This was the funny part, now the complicated one.
precedencegroup
For every operator, we can define the precedence using the keyword precedenceGroup
.
Read more also on Apple reference: https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations
precedencegroup PowerPrecedence { associativity: right higherThan: MultiplicationPrecedence } infix operator ^^^: PowerPrecedence func ^^^ (base: Int, power: Int) -> Double { return pow(Double(base), Double(power)) } let fiveSquared = 5 ^^^ 2 print( fiveSquared ) //fiveSquared: 25.0
It’s nice, but is better to do using generics, no?
You can find a recap here:
infix operator ^^ public func ^^ <T: Numeric>( value: inout T, power: Int) { var finalValue: T = 1 for _ in 0..<power { finalValue = finalValue * value } value = finalValue }
This operator, ^^ execute the “pow” function but don’t care of the type of the value.
So you can “pow” any type you want:
var floatValue: Float = 28.10 var doubleValue: Double = 10.84 var intValue: Int = 123 var binaryValue = 0b101 var octalValue = 0o11 var hexadecimalValue = 0xFA floatValue ^^ 2 print( floatValue ) //789.61005 hexadecimalValue ^^ 2 print(hexadecimalValue ) //62500
So, valid for any type of value!
Another interesting thing is about the structures like CGRect, CGPoint, and others…
You can create an operator, for instance, to add a “delta” to the x, y, w, h of a CGRect (don’t know the utility but..) or why not to sum two different CGRect:
infix operator ++ extension CGRect { static func +(lhs: CGRect, rhs: CGFloat) -> CGRect { return CGRect(x: lhs.origin.x + rhs, y: lhs.origin.y + rhs, width: lhs.size.width + rhs, height: lhs.size.height + rhs) } static func ++(lhs: CGRect, rhs: CGRect) -> CGRect { return CGRect(x: lhs.origin.x + rhs.origin.x, y: lhs.origin.y + rhs.origin.y, width: lhs.size.width + rhs.size.width, height: lhs.size.height + rhs.size.height) } }
To avoid operator caos in your code, add it in an extension like this example.
let frame1 = CGRect(x: 10, y: 10, width: 100, height: 100) let biggerFrame = frame1 + 10 print( "biggerFrame: \(biggerFrame)" ) let frame2 = CGRect(x: 30, y: 30, width: 300, height: 300) let frameSum = frame1 ++ frame2 print( "frameSum: \(frameSum)" )
Result is:
biggerFrame: (20.0, 20.0, 110.0, 110.0) frameSum: (40.0, 40.0, 400.0, 400.0)
You can create more and more based on your needed but…
Should You Use Operators?
As suggested in a talk by Erica Sadun, I report some notes about using operator and why and when to use, but also when to avoid!
Main points:
- Operators lack context
Operators, especially custom ones, naturally lack the context and cues that allow you to relate information and functionality to things that you are seeing in code as you look at it.
- Association
Association helps you to narrow down concepts by relating terms to an idea. Most of all, it assists recall. In Xcode, an associated word feeds into its auto-completion system in a way that pure symbols can’t.
- Unnatural operators
Unnatural operators aren’t naturally recognizable. Put yourself into the shoes of someone reading your code and not the person who wrote the operator.
- Someone will read your code
The compiler isn’t the only one who’s ever going to read your code. You are going to read your code as you write it, and you’re going to read it in a month or a year from now.
And important lessons:
Operators are precious and expensive. They involve significant mental costs for creating code for reading code, and maintaining code. Use operators sparingly.
A great operator that’s impossible to type isn’t going to get used.
Think global. First, prefer symbols that can be as easily entered on the German keyboard as a US keyboard.
When you adopt an operator use it, use it! If you’re not using an operator significantly more than you would a function, seriously consider cutting it out and just replace it with a function.
BONUS OPERATOR: “THE Flatter operator”
Given an array with optional values, return an array with only NOT nil values.
postfix operator .? postfix func .?<T>(lhs: Array<T?>) -> Array<T> { return lhs.flatMap{ $0 } } let numbers:[Int?] = [1, nil, 2, 3, nil, 6] print( numbers.? ) //[1, 2, 3, 6]
My favourite!
Enjoy, but don’t forget the suggestions!