Using Equatable and NilLiteralConvertible to re-implement Optionals in Swift (Part 2)

A few weeks ago I did a little thought experiment on how we might re-implement Swift’s optional type by using the Swift enum. If you haven’t read that yet, you can read it here. In this post, we’re going to push a little further and see if we can get something that corresponds a little more closely to the Swift Optional type.

This simply creates your standard Swift optional set to a nil value, then we can compare directly to nil and act upon this information. If we try this same code with our version of optional, we run in to a problem:

Or even worse, you might get an error about Optional<String?> not conforming to MirrorDisposition, this is basically Swift being unable to find comparison operators between Optional and nil… so it’s falling back to what it can find: MirrorDisposition, which is something Swift uses for some of the nifty IDE features.

So, what’s going on here?

Let’s dig a little deeper… If we comment out our comparisons and just put a breakpoint right after the setting of the myNilJOptional value, and check them out in the watch window, we see the following:

The JOptional is nil, but it has a ‘Some’ case instead of ‘None’, because we used the init(_ T) method to instantiate it, and the init method sets the Some case even if value is nil. As a result, JOptional is in fact *not* equal to nil!

We could modify this behavior by doing a check on that init method and setting None if the object is nil, but this introduces the issue of needing to update the case if the inner object is modified. This introduces a variety of issues, so what may be better is to simply implement two Swift interfaces that help solve this common problem: Equatable and NilLiteralConvertible.

Equatable just specifies that we are going to implement our own operator for ==, specific to our type.NilLiteralConvertible gives an interface to convert between JOptionals and nil literals. In our case, a nil object with type JOptional should be converted to JOptional.None.

So first, we add the interfaces to our JOptional definition:

enum JOptional<T> : Equatable, NilLiteralConvertible {
...

Next, we need to implement the equality operator. Because this is an operator overload, it goes in global space (outside of the enum):

func == <T>(lhs: JOptional<T>, rhs: JOptional<T>) -> Bool {
switch (lhs,rhs) {
case (.Some(let lhsVal), .Some(let rhsVal)):
// Both have a value, the *optionals* are equal, although the values might not be
return true
case (.None, .None):
// Both are nil
return true
default:
// One does not have a value, but the other does
return false
}
}

We’re using pattern matching of tuples here in order to handle three cases:

Both values are not-nil

Both values are nil

Values nil-ness does not match

It’s important to note, comparing two optional (even in Swift’s implementation) only compares that the case is the same (Some vs None) It does not compare the inner values, you need to unwrap for that behavior.

If we get a nil lhs or rhs value in this == operator, we need Swift to know to convert that to the .None case. So we implement the init(nilLiteral: ()) method.

init(nilLiteral: ()) {
self = None
}

So now, not only can we say things like this:

myNilJOptional = nil

And the result will be that myNilJOptional is set to JOptional.None

At the same time, we can do direct comparisons of JOptional values due to the equatable method, including to nil values. Like this:

Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials.
Available for pre-order today at a 50% discount.

Jameson Quave

Wanna get posts like these delivered to your email? Sign up for the mailing list.
I am an app developer in Austin, TX. I spend time dabbling with iPhone, iPad, Android, and web technologies. I write about technology, startups, and my technology & entrepreneurial experiments.