[Accepted] SE-230: Flatten nested optionals resulting from try?

Hello, Swift Community.

The review for SE-0230: Flatten nested optionals resulting from try? ran from September 29th through October 4th, 2018. You can find the review thread here.

There was a lot of discussion about this proposal. Many people find the current behavior inconvenient; the nested optional adds a lot of unwanted friction whenever they use try? with an optional-returning function. But there was also some concern about changing the behavior of try? this long after its introduction, and several people felt that this was just masking deeper problems with optional types.

Nested optional types are an unavoidable result of the basic design of optional types in Swift. They arise whenever optionality is added to a type, whether from a basic language feature (like as? X, which always returns a X?, even if X is itself an optional type) or just from generic programming (such as a function which returns a T?, when T may already be an optional type). Attempting to define them away would introduce major new semantic problems for these features; for example, if let on a T? in generic code would end up behaving differently when T was dynamically an optional type. Even if those problems were somehow manageable, it is far too late to consider such a radical change to the language. Arguments premised on the need to undertake drastic reform of optional types cannot be credited.

At the same time, nested optional types are usually quite frustrating to work with. Most of the language affordances for working with optional types only deal with a single level of optionality at a time; with a nested optional, they must be invoked repeatedly, which can be unexpected and awkward. So while the creation of nested optionals may be unavoidable, it isn't something we should do lightly, especially in syntax meant to be convenient. This is why e.g. ?-chaining collapses its resulting optional into an optional underlying result. try? has always been meant as a convenience feature, and the fact that it always wraps its result in an extra level of optionality instead of collapsing that optionality like ?-chaining does was an oversight in its implementation, not a principled decision.

How important is this problem to fix? It arises from a combination of two things: using the try? operator on a result that is already optional. This is, perhaps, not very common; it probably reflects two different methods of failure being used in one expression. But when it does happen, the extra optionality is almost always unwanted and causes significant friction for the programmer, demanding awkward workarounds: code like try? foo() has to be turned into something like (try? foo()) as? T. By forcing programmers to deal with the awkwardness of nested optionals more often, it raises the overall perceived complexity of working with optionals in Swift, and it makes try? feel unintuitive.

The Core Team does not want to make source-incompatible changes lightly, but we also want to leave room to improve the language for future users of Swift. We don't have a bright-line rule for when a change crosses the line to become unacceptable, but the key consideration in our analysis is the change's apparent impact in practice on existing code more than its hypothetical risks. In this case, we are convinced that the change leads to fairly inarguably better results.

Accordingly, SE-0230 is approved.

I'd like to thank everyone who participated in the evolution process for this proposal, from the pitch phase through the review, and especially its author, BJ Homer.

John McCall
Review Manager