If you’ve been following along, I’ve been struggling with the optional chaining syntax.
The Swift team has been fairly active in helping people with their troubles and confusion, and Chris Lattner responded to mine here (login required).
I won’t quote all of it (mainly because I don’t think I’m supposed to), but the gist of of it was this: if Swift supported implicit optional chaining, then it would be unclear which code was executed in the chain and which was not (this is my paraphrase, not Chris’ exact words).
An example:
No implicit chaining
my.delegate?.can().call()?.some(stuff())
foo.bar(baz())
With implicit chaining
my.delegate.can().call().some(stuff())
foo.bar(baz())
Chris’ argument is that with implicit chaining, the code above is ambiguous. In line #1, it’s explicit that there are many potential breaks along the code so it’s clear that everything to the right of a ?
has the potential of not being called. For line #2, it’s clear there no breaks. However, with implicit chaining, lines #1 and lines #2 do not carry that information and it’s unclear if stuff()
or baz()
are ever called.
I understand his point; afterall, it is clear (in any editing/reading environment) that there are multiple failure (or maybe more accurately, short-circuiting) points along the way when we use ?
. But, it still didn’t sit right with me.
I was asking myself why… I think I know why it didn’t sit well: I think the example is void of any and all context that could potentially already answer that question for us.
For example, if we know that Swift supports implicit optional chaining, then we already know that member lookups can potentially fail. Now, you might argue that I’ve simply made every member lookup ambiguous. But I don’t think that is the case either.
You see, the code samples above are all taken out of context. Code doesn’t live in isolation, but it participates in the context around it.
Here’s a snippet of code from a JSON tests:
func testValidateSingleValueNumberUsagePatternOptionalChaining() {
var json: JSValue = 123
let value = json.number?.distanceTo(100) ?? 0
XCTAssertEqual(value, -23)
}
If I had written:
func testValidateSingleValueNumberUsagePatternOptionalChaining() {
var json: JSValue = 123
let value = json.number.distanceTo(100) ?? 0
XCTAssertEqual(value, -23)
}
Or even:
func testValidateSingleValueNumberUsagePatternOptionalChaining() {
var json: JSValue = 123
let value = json.number.distanceTo(distanceToMoon()) ?? 0
XCTAssertEqual(value, distanceToMoon() - 123)
}
The code carries no loss in value. Even without that the aid of coding tools, the ??
operator already tells me I’m working with a left-hand side that is Optional<T>
and a righ-hand side this of type T
. Context is also how I know that the type of value
is Double
(not an Int
like you might be expecting) because JSValue.number
holds a Double?
.
I think it’s easy to take a code snippet, or worse, make up a line of code that also has no intrinsic meaning, and make a good case for why it has the potential to cause ambiguity. My argument is not that it’s not possible write ambiguous code with implicit chaining, but rather, that the context of your code ensures that the ambiguity is seldom, if ever, there.
In the end, I think it simply comes down to this:
Writing code is like writing in any language – often times we have constructs and words that look the same but are different, such as “I read a book yesterday” and “I read every morning”. The usage of “read”, when taking out of the context of its environment carries too little information to reveal its full meaning. However, once we provide the surrounding context, the meaning is made explicitly clear.
As a corrolary, we shouldn’t also then say that because I can write, “I read in the morning”, that the usage of “read” should not be allowed because it creates ambiguity: do I read every morning, or did I read this morning. Rather, we say that the sentence is ambiguous when taking out of its full context and we should add more context, or, if the statement is meant to stand on its own, make the disambiguate the meaning.
I think pragmatically, the use of Optional<T>
is the same: context reveals the explicit meaning of the code.