The Case for Implicit Optional Chaining

Update #2, Chris Lattner has stated this isn't a good idea because it can lead to ambiguity in code that may have side effects, such as foo.bar(baz()). The question is, does baz() get called or not. With foo?.bar(baz()) it is clear that there is a potential for baz() to not get called.

I understand what he is saying, though, I'm not sure I'm in agreement yet. Anyhow, it's nice to have this dialoge with the fine folks on the Swift team.

: .info

I've been wrestling with the verbosity of Optional Chaining for quite some time now. It started with my first JSON Parsing article and really hasn't gone away since then. Much of the work there was done to explicity hide the fact that a lookup could fail. Why do we need that? The language already has a construct to help us with this: Optionals.

Here's the example code that's essentially in the Swift Programming Language guide linked above:

class Residence {
    var numberOfRooms: Int = 1
}

class Person {
    var residence: Residence?
}

var john = Person()
// john.residence = Residence()  // uncomment this to toggle with if-else branch you get

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}

The above, the lone ? isn't that big of a deal. Though, I do think it's superfluous.

Let's get the cat out the bag: Swift is all about type inference. We can seldom look at code out of context and reason the type that is store in any given variable. For instance, what is the type for numberOfRooms? It's like an Int, but it could be Int?, Double, a UInt, maybe an Int8, or whatever.

There is only way to know: look at the definition.

A handy way to do that: ‚å• + Click

Showing type interference for 'let roomCount' is Int?

However, I'm also going to assert this: we do know the type while we are authoring the code. We know because we are going to use it and we need to know. This is subtley different from above but just as important. The context we are in gives us clarity over what it is.

This is what I think the code should look like:

if let roomCount = john.residence.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}

That's it, just remove the ?. Wait, doesn't that add confusion? How did numberOfRooms end up as an Int? requiring us to use if-let? Well, we know because at the time of authoring, we knew that residence is backed by Residence?. It doesn't matter when we come to modify the code how roomCount became an Int?, it only matters that it is one and you need to work with it as one.

The only time you will care why roomCount became an Int? is when you need to modify that variable itself, but then you are going to need to understand the entire chain john.residence.numberOfRooms at that point anyway. We've actually lost very little here and gained clarity in syntax.

Let's change the example above to contain a little richer data:

class RoomInfo {
    var description: String = ""
    var width: Double = 0
    var depth: Double = 0
}

class Residence {
    var rooms: [RoomInfo]? = nil
}

class Person {
    var residence: Residence?
}

var john = Person()

if let roomCount = john.residence?.rooms?.count {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}

This is where things are string to get really ugly for me. Why are there two ?? Yes, I know that both residence and rooms are an Optional<T>, but I don't really care. The purpose of the code is this: retrieve the number of rooms at John's residence. The ? do not help me get there. Further, I'll add that if you really care about the ?, you should be checking for which of the items, residence or rooms is nil, but the simple truth is this: we do not actually care; we only care about the end result in the chain.

So, instead, simply make the code what we want:

if let roomCount = john.residence.rooms.count {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}

The code above tells me one simple truth: when I get a roomCount, it's going to be an Optional<T>, because something down the chain failed; I do not care what failed, just that it failed and I'm going to handle that.

The code with the ?. tells me something different. It tells me that on your way to roomCount the members residence or rooms could have returned nil. I can get that info from the type declaration too, if I really wanted it. Don't make me repeat myself, especially when I'm doing so and it is not even important enough for me to do something about.

Update: August 13th, 2014

: .info

There was an interesting question asked about Optional<T> methods and extensions that collide with the type T. Here's an example (current Swift syntax; no implicit optional chaining):

extension Optional: Printable {
    public var description: String {
        return "a message for you"
    }
}

// Add the extension for String:
extension String: Printable {
    public var description: String {
        return "printable: string"
    }
}

let string: String? = nil
let s1 = string.description     // s1: String
let s2 = string?.description    // s2: String?

Today that is not ambiguous, we know which to call. With implicit chaining:

let string: String? = nil
let s1 = string.description

So, do we call the description on String or on Optional<T>?

I'm going to assert the following:

  1. We want to optimize for the non-Optional<T>; after all, that's why I want implicit chaining.
  2. The Optional<T> is already a special construct in the language and I'm ok with adding more rules in certain cases for when we need to deal with it directly as I think those cases are the exception, not the rule.

This means that I want the String.description version called.

If we really want the Optional<T> version, we have a way to do so:

let s1 = (string as Optional<String>).description

In the rare case where there is a collision, the compiler could do the following:

  1. Create an error or warning (I'd prefer a warning) that this is ambiguity, and
  2. Provide two "fix-it" options for the two valid cases for you to disambiguate, or
  3. Do nothing… I tend to think this is actually the right answer.

We could also (as mentioned by Wallacy Freitas on the devforums) simply invert the ? usage to treat it as explicitly working with the optional:

let s1 = string?.description

Again, I think this is simply an explicit example of the rare case. With the ? today, we need to always defend against this possibility. I would rather optimize for the normal case and provide a way around the corner case.

The Case for Implicit Optional Chaining