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

This post updated December 6, 2014 to reflect changes in Xcode 6.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.

In the last post we ended up with this class, JOptional:

enum JOptional<T> {
    case None
    case Some(T)
    
    init(_ value: T) {
        self = .Some(value)
    }
    
    init() {
        self = .None
    }
    
    func unwrap() -> Any {
        switch self {
        case .Some(let x):
            return x
        default:
            fatalError("Unexpectedly found nil while unwrapping an JOptional value")
        }
    }
    
}

…and a custom operator…

postfix operator >! {}
postfix func >! <T>(value: JOptional<T> ) -> Any {
    return value.unwrap()
}

It’s fairly straightforward to use the class by using the generic constructor, passing in your value, and then using our overloaded >! operator to unwrap.

// Instantiate some optionals
var myOptionalString = JOptional("A String!")
var a = myOptionalString.unwrap() // "A String!"
var c = myOptionalString>!     // "A String!"

In Swift’s actual Optional implementation, as of Xcode 6 Beta 5, Optionals can also be compared to nil, without doing any unwrapping first. For example:

var myNilValue : String? = nil

if(myNilValue == nil) {
    println("This is nil!")
}
else {
    println("This isn't nil, carry on")
}
This is nil!

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:

var myNilJOptional = JOptional(myNilValue)

if(myNilJOptional == nil) {
    println("This is nil!")
}
else {
    println("This isn't nil, carry on")
}
This isn't nil, carry on

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:

  1. Both values are not-nil
  2. Both values are nil
  3. 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:

if(myNilJOptional == nil) {
  // Do something
  println("This JOptional is nil!")
}

Checking out earlier code again, we get the expected result:

This is nil!
This is nil!

Full source code of Part 2 found here &raqou;

This topic is explored more in-depth in my upcoming book.

In the meantime, take a look at my other tutorials and posts about Swift.


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.

Subscribe via RSS

Re-implementing Optionals using Swift’s powerful enum type.

This post updated December 6, 2014 to reflect changes in Xcode 6.2

If you’ve been messing with Swift much lately, you may have noticed that enum’s do much more than they did in Objective-C, or in most other languages for that matter. In the WWDC videos discussing Swift, one of the presenters mentions that Optionals are implemented as enums. Let’s explore this a bit and see if we could possibly implement Optionals ourselves, as an exercise in exploring Swift’s enum type.

The first thing to note about an Apple’s implementation of Optional is that it’s an enum with two cases, Some and None. So first off, we create an enum, let’s call it JOptional, and add these cases.

enum JOptional {
    case None
    case Some
}

When we set the enum case to ‘Some’, we should also have an associated value, but it can be any object type. So we use the T keyword to specify it’s a generic type.

enum JOptional<T> {
    case None
    case Some(T)
}

We haven’t built out any fancy shortcuts that Swift’s regular optionals use yet, but we can still instantiate JOptionals directly if we add some init() methods.

enum JOptional<T> {
    case None
    case Some(T)
    
    init(_ value: T) {
        self = .Some(value)
    }
    
    init() {
        self = .None
    }
    
}

// Instantiate some optionals
var myOptionalString = JOptional("A String!")
var myNilString = JOptional()

Already we have a bit of a shortcut baked in here. The type of myOptionalString is JOptional<String>, but because we added a init(_ value: T) method to the enum, we can pass in:

  1. An unnamed parameter, due to the use of the _ for external name
  2. Of any type, since it is accepting a generic type of value T
  3. That initializes self, and sets the case to .Some with the attached value

Taking a look at the value of myOptionalString (I’m using a playground for this) you should see (Enum value). This is similar to how Swift’s optionals are “wrapped”. So, we need an unwrap method. Here is what I came up with:

func unwrap() -> Any {
    switch self {
    case .Some(let x):
        return x
    default:
        assert(true, "Unexpectedly found nil while unwrapping an JOptional value")
    }
    return JOptional.None
}

This method uses the value-binding feature of Swift to determine if the enum value can be matched against .Some(let x):
If it can, then it’s safe to assume that x actually contains something, and since this is an unwrapping, we can return it.

Otherwise, this falls back to the default case, which is where we need to point out to the developer that they attempted to unwrap a JOptional that wasn’t able to be bound to x. I added an assert here, I believe this is also present in Apple’s Optional enum, since a force unwrapping of an Optional produces a hard crash.

We can test this out, like so:

var myOptionalString = JOptional("A String!")
var a = myOptionalString.unwrap()

The value of a should now be to “A String!”, while myOptionalString should still be JOptional.Some(“A String!”), which will print as (Enum value).

In Apple’s Optional, one can unwrap by just appending ! to the end of the variable. We can’t use this symbol since it’s reserved, but there’s no reason we can add >! as a new postfix operator. Why >! you ask? I have no idea, but it’s not taken. Actually, this is a pretty good example of how to not choose operator overloads, but that’s okay. No one is ever going to use this, I hope.

On line 1 we create the postfix, then on line 2 we implement the overload to the operator

postfix operator >! {}
postfix func >! <T>(value: JOptional<T> ) -> Any {
    return value.unwrap()
}

Now, we can unwrap the optional with our custom operator

// Instantiate some optionals
var myOptionalString = JOptional("A String!")
var a = myOptionalString.unwrap() // "A String!"
var c = myOptionalString>!     // "A String!"

Full code for this part here »

This concludes part 1. In the next part we’ll implement nil comparison using the Equatable and NilLiteralConvertible interfaces.
Go To Part 2 »

This topic is also explored more in-depth in my upcoming book.


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.

Subscribe via RSS