How the Nil coalescing operator taught me @autoclosure (and @noescape)

The best way to truly understand a language is to try and implement some of its core features. With some instruction from this fantastic book on functional programming in swift I reimplemented the nil coalescing operator and learnt a little bit about the nature of the Swift language on the way.

To start of I defined my very own custom operator (not something I would usually recommend) as follows

infix operator <<??>> {}

That was super easy! Now to actually implement the operation, it would seem this operator needs the to take in an optional T? and a non optional T and return a T. That in mind we can construct our function.

func <<??>><T>(optional: T?, defaultValue: T) -> T {
    guard let unwrapped = optional else {return defaultValue}
    return unwrapped
}

This looks pretty good, it seems to cover exactly what I had in mind and was super simple to implement. Operators aren't that scary after all! To test the behaviour of my new operator I define the following struct.

struct TestingStruct {
    func testNilCoalescingOperator(optional: Int?) -> Int {
        return optional <<??>> someOtherInt()
    }
    
    func someOtherInt() -> Int {
        print("I was executed....")
        return 7
    }
}

Hmmmm there seems to be a slight problem here, when I call testNilCoalescingOperator(5) I see "I was executed...." printed in the console. When I do this with the ?? operator built in to swift that function is not executed and we do not see the message printed to console. That is much better we don't want to execute unnecessary code, what if that function is computationally expensive? It seems there is still work to be done on my operator.

To stop immediate execution of the function we redefine our operator as so

func <<??>><T>(optional: T?, defaultValue: () -> T) -> T {
    guard let unwrapped = optional else {return defaultValue()}
    return unwrapped
}

We now call our operator slightly differently as we just pass a reference to the function

return optional <<??>> someOtherInt

This seems nice, if we compare with the inbuilt ?? operator we see a problem however, passing just the function reference this way causes the following error 'Binary Operator cannot be applied to operands of type Int? and () -> Int'. There is a very good reason ?? is not built this way and it is to deal with the following use cases

struct TestingStruct {
    var intProperty = 7
    func testNilCoalescingOperator(optional: Int?) -> Int {
        return optional ?? intProperty
    }
}

or even more simply

return optional ?? 7

If we try these use cases with our current implementation we have to wrap the argument in a closure as follows

return optional <<??>> { self.intProperty }

and

return optional <<??>> { 7 }

This looks very different to our ?? operator, for a start they never have to reference self when passing arguments to this function and they don't need to wrap the arguments in a closure (remember that @autoclosure from the title?). First things first lets get rid of the self.

func <<??>><T>(optional: T?, @noescape defaultValue: () -> T) -> T {
    guard let unwrapped = optional else {return defaultValue()}
    return unwrapped
}

Now I can call my operator with a property like so

return optional <<??>> { intProperty }

Yay magic, well not magic. The @noescape parameter declares the closure passed in to the function will only be used in the scope of that function. In fact if I try to store that function to a non local variable or pass it to another function the compiler will give me a warning!

We still have that pesky closure though, so to fully implement our operator we will redefine <<??>> for the last time,

func <<??>><T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
    guard let unwrapped = optional else {return defaultValue()}
    return unwrapped
}

We have replaced the @noescape with @autoclosure. @autoclosure automatically wraps the statement in a closure which is marked as @noescape by default (thus we remove the @noescape as it is implied by @autoclosure). We now have a full implementation of <<??>> that behaves the same as ??.

Rebuilding things in Swift is pretty fun and a great way to learn. With the language now open source there is no excuse not too as you can always peek at the final answer :P

Below is the final full example.

infix operator <<??>> {}

func <<??>><T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
    guard let unwrapped = optional else {return defaultValue()}
    return unwrapped
}

struct TestingStruct {
    var intProperty = 7
    func testNilCoalescingOperator(optional: Int?) -> Int {
        return optional <<??>> intProperty
    }
}

let test = TestingStruct()
test. testNilCoalescingOperator(.None)