Swift Advanced Operators - Custom and Overloaded Operators
Welcome to Swift Advanced Operators! Beyond the basic operators, Swift allows you to define custom operators and overload existing ones to work with your custom types. This powerful feature lets you write expressive, elegant code that feels natural. In this guide, we’ll explore how to create and use advanced operators effectively.
What are Advanced Operators?
Advanced operators let you:
- Overload existing operators for custom types
- Create entirely new custom operators
- Define precedence and associativity
- Write expressive, mathematical code
Why Use Advanced Operators?
- ✅ Expressiveness - Natural, readable syntax
- ✅ Mathematics - Elegant mathematical operations
- ✅ DSLs - Domain-specific languages
- ✅ Convenience - Intuitive APIs
Operator Types:
- Prefix (
-x) - Infix (
x + y) - Postfix (
x++)
Operator Overloading
Override existing operators to work with your custom types.
Basic Arithmetic Operators
struct Vector2D { var x: Double var y: Double}
// Addition operatorextension Vector2D { static func + (left: Vector2D, right: Vector2D) -> Vector2D { return Vector2D(x: left.x + right.x, y: left.y + right.y) }}
let vector1 = Vector2D(x: 3.0, y: 1.0)let vector2 = Vector2D(x: 2.0, y: 4.0)let sum = vector1 + vector2print("Sum: (\(sum.x), \(sum.y))") // Sum: (5.0, 5.0)
// Subtraction operatorextension Vector2D { static func - (left: Vector2D, right: Vector2D) -> Vector2D { return Vector2D(x: left.x - right.x, y: left.y - right.y) }}
let difference = vector2 - vector1print("Difference: (\(difference.x), \(difference.y))") // Difference: (-1.0, 3.0)
// Multiplication by scalarextension Vector2D { static func * (vector: Vector2D, scalar: Double) -> Vector2D { return Vector2D(x: vector.x * scalar, y: vector.y * scalar) }}
let scaled = vector1 * 3.0print("Scaled: (\(scaled.x), \(scaled.y))") // Scaled: (9.0, 3.0)Comparison Operators
extension Vector2D: Equatable { static func == (left: Vector2D, right: Vector2D) -> Bool { return left.x == right.x && left.y == right.y }}
let v1 = Vector2D(x: 1.0, y: 2.0)let v2 = Vector2D(x: 1.0, y: 2.0)let v3 = Vector2D(x: 3.0, y: 4.0)
print(v1 == v2) // trueprint(v1 == v3) // false
// Not equal is automatically providedprint(v1 != v3) // truePrefix and Postfix Operators
extension Vector2D { // Prefix - (negation) static prefix func - (vector: Vector2D) -> Vector2D { return Vector2D(x: -vector.x, y: -vector.y) }}
let vector = Vector2D(x: 3.0, y: -4.0)let negated = -vectorprint("Negated: (\(negated.x), \(negated.y))") // Negated: (-3.0, 4.0)
// Prefix ++ (increment)extension Vector2D { static prefix func ++ (vector: inout Vector2D) -> Vector2D { vector.x += 1 vector.y += 1 return vector }}
var mutableVector = Vector2D(x: 1.0, y: 2.0)let incremented = ++mutableVectorprint("Incremented: (\(incremented.x), \(incremented.y))") // (2.0, 3.0)Compound Assignment Operators
extension Vector2D { static func += (left: inout Vector2D, right: Vector2D) { left = left + right }
static func -= (left: inout Vector2D, right: Vector2D) { left = left - right }
static func *= (left: inout Vector2D, scalar: Double) { left = left * scalar }}
var vector = Vector2D(x: 1.0, y: 2.0)vector += Vector2D(x: 3.0, y: 4.0)print("After +=: (\(vector.x), \(vector.y))") // (4.0, 6.0)
vector *= 2.0print("After *=: (\(vector.x), \(vector.y))") // (8.0, 12.0)Custom Operators
Create entirely new operators with custom symbols.
Declaring Custom Operators
// Infix operator for dot productinfix operator •: MultiplicationPrecedence
extension Vector2D { static func • (left: Vector2D, right: Vector2D) -> Double { return left.x * right.x + left.y * right.y }}
let v1 = Vector2D(x: 3.0, y: 4.0)let v2 = Vector2D(x: 2.0, y: 1.0)let dotProduct = v1 • v2print("Dot product: \(dotProduct)") // 10.0
// Cross product operatorinfix operator ×: MultiplicationPrecedence
extension Vector2D { static func × (left: Vector2D, right: Vector2D) -> Double { return left.x * right.y - left.y * right.x }}
let crossProduct = v1 × v2print("Cross product: \(crossProduct)") // -5.0Power Operator
infix operator **: MultiplicationPrecedence
extension Int { static func ** (base: Int, power: Int) -> Int { return Int(pow(Double(base), Double(power))) }}
extension Double { static func ** (base: Double, power: Double) -> Double { return pow(base, power) }}
let result1 = 2 ** 3print("2^3 = \(result1)") // 8
let result2 = 2.0 ** 0.5print("2^0.5 = \(result2)") // 1.414...Range Operator
infix operator ..<>: RangeFormationPrecedence
extension Int { static func ..<> (start: Int, end: Int) -> [Int] { return Array(start..<end) }}
let range = 1 ..<> 5print(range) // [1, 2, 3, 4]Precedence and Associativity
Control how operators group and evaluate.
Precedence Groups
precedencegroup ExponentiationPrecedence { higherThan: MultiplicationPrecedence associativity: right}
infix operator ^^: ExponentiationPrecedence
extension Double { static func ^^ (base: Double, exponent: Double) -> Double { return pow(base, exponent) }}
// Right associative: evaluates 2 ^^ (3 ^^ 2)let result = 2.0 ^^ 3.0 ^^ 2.0print(result) // 512.0 (2^9, not 8^2)Custom Precedence Example
precedencegroup ConditionalPrecedence { lowerThan: NilCoalescingPrecedence associativity: right}
infix operator ??: ConditionalPrecedence
func ?? <T>(condition: Bool, values: (T, T)) -> T { return condition ? values.0 : values.1}
let value = true ?? ("yes", "no")print(value) // "yes"Practical Examples
Example 1: Matrix Operations
struct Matrix { var rows: Int var columns: Int var grid: [Double]
init(rows: Int, columns: Int, defaultValue: Double = 0.0) { self.rows = rows self.columns = columns self.grid = Array(repeating: defaultValue, count: rows * columns) }
func indexIsValid(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns }
subscript(row: Int, column: Int) -> Double { get { assert(indexIsValid(row: row, column: column)) return grid[(row * columns) + column] } set { assert(indexIsValid(row: row, column: column)) grid[(row * columns) + column] = newValue } }}
// Matrix additionextension Matrix { static func + (left: Matrix, right: Matrix) -> Matrix { guard left.rows == right.rows && left.columns == right.columns else { fatalError("Matrix dimensions must match") }
var result = Matrix(rows: left.rows, columns: left.columns) for i in 0..<left.rows { for j in 0..<left.columns { result[i, j] = left[i, j] + right[i, j] } } return result }}
// Matrix multiplicationextension Matrix { static func * (left: Matrix, right: Matrix) -> Matrix { guard left.columns == right.rows else { fatalError("Cannot multiply: left.columns must equal right.rows") }
var result = Matrix(rows: left.rows, columns: right.columns) for i in 0..<left.rows { for j in 0..<right.columns { var sum = 0.0 for k in 0..<left.columns { sum += left[i, k] * right[k, j] } result[i, j] = sum } } return result }}
var matrix1 = Matrix(rows: 2, columns: 2)matrix1[0, 0] = 1matrix1[0, 1] = 2matrix1[1, 0] = 3matrix1[1, 1] = 4
var matrix2 = Matrix(rows: 2, columns: 2)matrix2[0, 0] = 2matrix2[0, 1] = 0matrix2[1, 0] = 1matrix2[1, 1] = 2
let sum = matrix1 + matrix2print("Sum[0,0]: \(sum[0, 0])") // 3
let product = matrix1 * matrix2print("Product[0,0]: \(product[0, 0])") // 4Example 2: Complex Numbers
struct Complex { var real: Double var imaginary: Double
var description: String { if imaginary >= 0 { return "\(real) + \(imaginary)i" } else { return "\(real) - \(abs(imaginary))i" } }}
extension Complex { static func + (left: Complex, right: Complex) -> Complex { return Complex(real: left.real + right.real, imaginary: left.imaginary + right.imaginary) }
static func - (left: Complex, right: Complex) -> Complex { return Complex(real: left.real - right.real, imaginary: left.imaginary - right.imaginary) }
static func * (left: Complex, right: Complex) -> Complex { let real = left.real * right.real - left.imaginary * right.imaginary let imaginary = left.real * right.imaginary + left.imaginary * right.real return Complex(real: real, imaginary: imaginary) }}
let c1 = Complex(real: 3, imaginary: 4)let c2 = Complex(real: 1, imaginary: 2)
let sum = c1 + c2print(sum.description) // 4.0 + 6.0i
let product = c1 * c2print(product.description) // -5.0 + 10.0iExample 3: Measurement Units
struct Distance { var meters: Double
var kilometers: Double { return meters / 1000 }
var miles: Double { return meters / 1609.34 }}
extension Distance { static func + (left: Distance, right: Distance) -> Distance { return Distance(meters: left.meters + right.meters) }
static func - (left: Distance, right: Distance) -> Distance { return Distance(meters: left.meters - right.meters) }
static func * (distance: Distance, scalar: Double) -> Distance { return Distance(meters: distance.meters * scalar) }
static func / (distance: Distance, scalar: Double) -> Distance { return Distance(meters: distance.meters / scalar) }}
extension Distance: Comparable { static func < (left: Distance, right: Distance) -> Bool { return left.meters < right.meters }}
let d1 = Distance(meters: 1000)let d2 = Distance(meters: 500)
let total = d1 + d2print("Total: \(total.kilometers) km") // 1.5 km
let longer = d1 > d2print("d1 > d2: \(longer)") // trueExample 4: Functional Composition
infix operator >>>: AdditionPrecedence
func >>> <A, B, C>(f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C { return { a in g(f(a)) }}
let addOne = { (x: Int) -> Int in x + 1 }let double = { (x: Int) -> Int in x * 2 }let square = { (x: Int) -> Int in x * x }
let combined = addOne >>> double >>> squarelet result = combined(3)print(result) // ((3 + 1) * 2)^2 = 64Example 5: String Concatenation with Separator
infix operator <+>: AdditionPrecedence
extension String { static func <+> (left: String, right: String) -> String { return left + " " + right }}
let greeting = "Hello" <+> "World"print(greeting) // "Hello World"
let sentence = "Swift" <+> "is" <+> "awesome"print(sentence) // "Swift is awesome"Best Practices
1. Use Operators Sparingly
// ✅ Good - clear and intuitiveextension Vector { static func + (left: Vector, right: Vector) -> Vector { }}
// ❌ Bad - obscure meaninginfix operator ⚡extension Vector { static func ⚡ (left: Vector, right: Vector) -> Vector { }}2. Match Expected Behavior
// ✅ Good - behaves like standard +extension Point { static func + (left: Point, right: Point) -> Point { return Point(x: left.x + right.x, y: left.y + right.y) }}
// ❌ Bad - unexpected behaviorextension Point { static func + (left: Point, right: Point) -> Point { return Point(x: left.x * right.x, y: left.y * right.y) // This should be * }}3. Document Custom Operators
/// Calculates the dot product of two vectors/// - Returns: Scalar value representing the dot productinfix operator •: MultiplicationPrecedence
extension Vector { static func • (left: Vector, right: Vector) -> Double { return left.x * right.x + left.y * right.y }}4. Choose Appropriate Precedence
// ✅ Good - proper precedenceprecedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence associativity: right}
infix operator **: PowerPrecedence5. Avoid Overusing Custom Operators
// ✅ Better - use methods for complex operationsextension Array { func chunked(into size: Int) -> [[Element]] { // Implementation return [] }}
// ❌ Avoid - unclear custom operatorinfix operator ÷÷extension Array { static func ÷÷ (array: Array, size: Int) -> [[Element]] { // Same implementation return [] }}Summary
Advanced operators enable expressive, elegant code:
Operator Overloading 🔧
- Override for custom types
- Arithmetic, comparison, compound
- Prefix, infix, postfix
Custom Operators ⚡
- Create new operators
- Define symbols
- Mathematical operations
Precedence 📊
- Control evaluation order
- Define precedence groups
- Left/right associativity
Best Practices ⭐
- Use sparingly
- Match expected behavior
- Document well
- Choose clear symbols
Practice Exercises
- Create a Fraction struct with arithmetic operators
- Implement a Money type with currency operations
- Build a Temperature type with unit conversions
- Create operators for set operations
- Implement matrix operations
- Build a physics calculation library
Master advanced operators to write elegant, expressive code! ✨
Remember: With great power comes great responsibility - use operators wisely!