Swift Operators Are Functions

by Louis Tur

As previously discussed, the turning point for me with Swift was watching a func converted into a succinct three character statement. Summarily, the code looks like:

This is possible in part thanks to Swift’s emphasis on types. In Swift, a function that expects another function as a parameter, is really checking for the function's type to match. In the case of the code above, the performOperation method is expecting a type of (Double, Double) -> Double. So as long as the function we pass matches that type, we can ensure that Swift will accept it as being valid.

What's of particular interest here is that all operators (unary, binary, and tertiary) are treated as functions in Swift. In the example above, we substitute the multiply function with the binary operator *. To really explain why this is possible, let's for a moment look at Swift's declaration of the * operator:

infix operator * {  
    associativity left
    precedence 150
}

As Mattt of NSHipster and Tammo Freese (via Medium) explain, the declaration defines a few things:

  1. The symbol for the operator will be *
  2. It will be infix, meaning in-between two operands
  3. And additionally it details its precedence (order of operation) and associativity (rules for handling operators of equal precedence).

In order to actually use an operator, Swift requires that you define functions that describe its use-cases. There are many for each operator to allow for the multitude of different types the operator could be used with. For example, just three of the * operator's (many) functions are:

func *(lhs: Double, rhs: Double) -> Double  
func *(lhs: Float, rhs: Float) -> Float  
func *(lhs: Int8, rhs: Int8) -> Int8  
...

These functions, and Swift's type inference, are what allow for implicitly handling any kind of multiplication without having to include the operand's type when using the * operator.


Bringing it together

If you recall, in the snippet from earlier we wrote

func performOperation(operation: (Double, Double) -> Double ) { ... }  

which was called in a switch statement

case "*": performOperation(multiply)  

where

func multiply(opt1: Double, opt2: Double) -> Double { }  

performOperation is just interested in a parameter of type Double, Double -> Double to accept. Which just so happens to be exactly one of *'s function type:

func *(lhs:Double, rhs:Double) -> Double  

Because *'s type matches the expected type for performOperation we can really see why the substitution works!

Isn't the esoteric interesting?


Oh, and P.S. We can simplify our expression even further to just be

case "*": performOperation(*)  

This is because we don't change *'s type by omitting the two variables Swift implicitly adds for unnamed parameters. And really, they could just be added later within performOperation.

Louis Tur

"How" has been the single most used word in my literary arsenal for as long as I can remember. I've never really been satisfied knowing that something works, but only by knowing how it works.

Read more from this author