Implicitly Unwrapped Optionals In Depth

In my [last post], I took a detailed look at how Swift’s optional chaining works, and (in passing) at the mechanics of the if-let construct. That accounts for about half the story of optionals. The other half is the story of [implicitly unwrapped optionals][].

To review, implicitly unwrapped optionals allow us to treat them as if they weren’t optionals at all. Looking at our example from last post, if we change things from optionals to implicitly unwrapped optionals, we can write:

public class Demo {
    public let subDemo:SubDemo!
    init(subDemo sDemo:SubDemo = nil) {
        self.subDemo = sDemo
    }
}

public class SubDemo {
    public let count:Int = 1
}

let aDemo:Demo! = nil
let bDemo:Demo! = Demo()
let cDemo:Demo! = Demo(subDemo: SubDemo())

let aCount = aDemo.subDemo.count // CRASH
let bCount = bDemo.subDemo.count // CRASH
let cCount = cDemo.subDemo.count // 1

The rule is that implicitly unwrapped optionals must not be nil by the time they are used—if they are nil, the program will throw a runtime error and crash. This is primarily useful in two-part initialization schemes, like when views and view controllers are loaded from nibs and their outlets get set after class instantiation, but before we try to access them.

Which is fine and dandy, buy we once again have a very convenient syntax that can obscure what’s really going on.

• • • • •

A few days ago, someone posted on the developer forums a snippet essentially equivalent to this, which does not work in Xcode 6 Beta 4:

let arr:[String]! = ["hello", "world"]
let val = find(arr, "hello")

The error is:

type ‘[String]!’ does not conform to protocol ‘Collection’

This, however, does work:

let val = find(arr!, "hello") // val = {Some 0}

So what gives?

To be clear, the fact that there was an error is almost certainly a bug in Swift, since Swift promises that implicitly unwrapped optionals can be used anywhere as if they were non-optional types. But it does bring to light a very important point:

Explicitly unwrapped optionals are still optionals.

When we write let arr = [String]!, we aren’t cheating and underhandedly telling the compiler “arr is a [String] type, trust me”. We are actually declaring arr to be an ImplicitlyUnwrappedOptional<[String]>. The exclamation mark is simply shorthand—all behaviors are implemented as methods, like for any type.

Given that, it’s clear that unless ImplicitlyUnwrappedOptional conforms to the Collection protocol, which it doesn’t, it will trigger a type error in that snippet. Conversely, since the call to arr! explicitly unwraps the optional, the type error is resolved. The only thing that isn’t happening is the implicit conversion that Swift promises.

What this means is that ! is a shorthand in a type declaration, but that when it follows an object, it is an operator. And since Swift allows us to declare operators, we can implement it ourselves—in a way that will surprise absolutely no one at this point:1

postfix operator ! {}
@postfix func !<T>(opt:T!) -> T {
    switch opt {
    case .Some(let x):
        return x
    case .None
        abort()
    }
}

The only thing that may be unexpected is the abort() call. The abort function is labeled @noreturn, which means it will not return to its caller; therefore, the type system accepts the fact that it does not return a T. The real implementation uses a different function that also prints out a message explaining the reason for the crash, but the end result is the same: if the optional is nil, calling ! on it leads to a runtime error.

And with that implementation, it’s clear why the corrected code compiled: arr! returns a [String] type, which does implement Collection. Bug notwithstanding, everything makes sense.

• • • • •

Good; we have figured out how ?. and ! work. There is still one question left: why do regular method calls work on an implicitly unwrapped optional? To answer that question, we need the [insight][] provided by Ole Begeman: methods are curried functions. In other words, a, b and c are all valid expressions in the code below:

let str = "hello"

let a = str.stringByAppendingString("!") \\ "hello!"
let b = String.stringByAppendingString(str)("!") \\ "hello!"

let method = String.stringByAppendingString(str)
let c = method("!") \\ "hello!"

So now, let’s think about method calls. ?. is syntactic sugar for flatMap. Let’s imagine a function, say +-, that would do the work of executing a method call:

operator infix +- { associativity left }
@infix func +-<T,Z>(obj:T, f:T->Z) -> Z {
    return f(obj)
}

We could call any method with this operator like so:2

let happyHello = "hello" +- { 
    String.stringByAppendingString($0)("!") 
}

Wait a minute. That looks exactly like [our call to |-][optional-chaining] for regular optionals. In fact, if we override +-

operator infix +- { associativity left }
@infix func +-<T,Z>(obj:ImplicitlyUnwrappedOptional<T>, f:T->Z)
    -> ImplicitlyUnwrappedOptional<Z> {
    switch obj {
    case .Some(let x):
        return f(x)
    case .None:
        abort()
    }
}

… we have another version of |-, which can be used in the exact same way:

let maybeHello:String! = "hello"
let happyHello = maybeHello +- {
    String.stringByAppendingString($0)("!")
} +- {
    String.stringByAppendingString($0)("!")
}
// happyHello = "hello!!"

However, it would behave correctly for implicitly unwrapped optionals: if maybeHello were nil, the code would abort, as prescribed.

But this also means that that the +- operator can be transformed into a call to an implementation of flatMap on an ImplicitlyUnwrappedOptional. Thus, just like ?. is syntactic sugar for flatMap on Optional, the regular dot operator that we use to call methods is ultimately syntactic sugar for flatMap on ImplicitlyUnwrappedOptional.3

Isn’t that flatMap method interesting?

• • • • •

The final conclusion, then, is that every aspect of optionals can be implemented in pure Swift, despite the code being clunkier than the specialized syntax. It’s great that we are given tools to simplify dealing with them, given how important they are to Objective-C interop; I intend to use them to their fullest. But I also hope these last two posts help clarify what is going on under the hood.

So if you ever find optional behavior that you don’t understand, take a close look at the actual semantics; odds are it’ll narrow down the source of the error. And who knows, it might even be a language bug. How often do we get to say that?

• • • • •

But, really, isn’t that flatMap method interesting?







1 This implementation doesn’t actually work, though, because ! is a reserved operator. *wink wink nudge nudge*. ↩︎


2 In this case, the . in String.stringByAppendingString is not a method call, but more of a record lookup. Since it has different semantics, we’re not actually cheating when we use it inside a function intended to replace the . method-call operator.↩︎


3 It’s actually a little more complicated than that. The dot operator needs to work both on regular types and on ImplicitlyUnwrappedOptionals, so the actual implementation of the syntactic sugar isn’t as straightforward. However, the core idea is the same. ↩︎

[implicitly unwrapped optionals]: http://commandshift.co.uk/blog/2014/07/20/swift-implicitly-unwrapped-optionals
[last post]: http://nomothetis.svbtle.com/understanding-optional-chaining
[insight]: http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/
[all the way down]: http://en.wikipedia.org/wiki/Turtles_all_the_way_down
[optional-chaining]: http://nomothetis.svbtle.com/understanding-optional-chaining#chaining-example

 
89
Kudos
 
89
Kudos

Now read this

Addendum: Deriving the Third Monad Law From Nested Comprehensions

The third monad law declares that the following identity must hold for a monad M, where a:M<A>, f: A -> M<B>, and g: B -> M<C>: (a >>=- f) >>=- g == a >>=- { b in f(x) >>=- g } To motivate... Continue →