Swift – Custom Operators

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 prefixinfix or postfix modifiers:

prefix[postfix, infix] operator %

This means that your new operator (%) can be used as:

  • postfix like 5%
  • prefix like %5
  • infix 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 operatorprefix func (lhs: Double) -> Double {
    return sqrt(lhs)

}

let someVal: Double = 25let squareRoot = someValprint( 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 operatorfunc (lhs: Double, rhs: Double) -> Double {
    return lhs * lhs + rhs * rhs
}

let doubleVal1 = 2.0let doubleVal2 = 3.0

let squareSum = doubleVal1doubleVal2print( "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 ^^^: PowerPrecedencefunc ^^^ (base: Int, power: Int) -> Double {
    return pow(Double(base), Double(power))
}

let fiveSquared = 5 ^^^ 2print( fiveSquared ) //fiveSquared: 25.0

It’s nice, but is better to do using generics, no?

You can find a recap here:

Swift – Use generics [T] with closures

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.10var doubleValue: Double = 10.84var intValue: Int       = 123var binaryValue         = 0b101var octalValue          = 0o11var hexadecimalValue    = 0xFA

floatValue ^^ 2print( floatValue ) //789.61005

hexadecimalValue ^^ 2print(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 + 10print( "biggerFrame: \(biggerFrame)" )

let frame2 = CGRect(x: 30, y: 30, width: 300, height: 300)
let frameSum = frame1 ++ frame2print( "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!

 

Alberto Pasca

Software engineer @ Pirelli & C. S.p.A. with a strong passion for mobile  development, security, and connected things.