Swift Operators Are Functions
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:
- The symbol for the operator will be
*
- It will be
infix
, meaning in-between two operands - And additionally it details its
precedence
(order of operation) andassociativity
(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
.