SE-0230: Flatten nested optionals resulting from `try?`

It's referred to as "try operator" in TSPL:

Try Operator

A try expression consists of the try operator followed by an expression that can throw an error. It has the following form:

  1. try expression

So in the case of:

try? throwingFoo() as? Bar

the "expression" of that try? operator is
throwingFoo() as? Bar
though people (including me) seems to intuitively expect it to be
throwingFoo()

I can totally understand why exprOfTypeU as? T always results in T?, no matter the type of U. I can't understand why try? shouldn't always just add one optional layer (as it currently does), a layer that represents the error-handling / throwing that it's whole point is to convert into an optional layer.

1 Like

Odd... it's not mentioned in Operator Declarations | Apple Developer Documentation
(but Core is typing, no need for speculation ;-)

Just realized that we most likely wouldn't introduce try? at all if it wouldn't already be part of the language:
There's quite a big controversy around for?, and it's not only trivial to implement a try-function - that function wouldn't have the problem that SE-0230 wants to solve...

The try operators are prefix operators that effectively have minimum precedence w.r.t all productions to their right and which are themselves forbidden to appear on the right of any non-assignment infix operator (or to the right of an assignment operator that appears to the left of an infix operator with lower precedence than assignment; no such operators exist by default, but they can be created). In other words, they are rather unique grammatically.

I do not understand the point of view of "changing the type-checking rule will break source compatibility, so let's start messing with the grammar". Changing parsing rules is far more likely to have unpleasant ramifications that you aren't expecting.

try? foo() == nil does not work, because the equality operator parses within the try?. It would need to be (try? foo()) == nil. Also, it discards the actual result of the expression, so it's unlikely to be useful with an expression that produces a non-Void type, much less specifically an optional type. This proposal does not change its usefulness with Void.

The review period for this proposal ended on Monday. It was not an especially short review period. I'll roll up the late feedback, but please try to review within the time period in the future; it's only by coincidence that we haven't already made a decision on the Core Team.

3 Likes

I agree. It is trivial to implement Swift's current try? as a function, and it's really easy to find a (much too long) fully descriptive name and to describe its complete behavior.

/// Evaluates a closure and converts its error handling layer to an optional layer.
func withErrorLayerConvertedToOptionalLayer<T>(_ closure: @autoclosure () throws -> T) -> T? {
    do {
        return try closure()
    } catch _ {
        return nil
    }
}

And I agree that it might work better as a function than an operator with the current precedence, as it doesn't subject its users to the common pain point of the try? operator:

func throwingFoo<T>(_ v: T) throws -> Any { return v }

let r1: Int?? = try? throwingFoo(123) as? Int // try <-> as precedence makes type of r1 Int?? rather than Int?

let r2: Int? = withErrorLayerConvertedToOptionalLayer(try throwingFoo(123)) as? Int

A corresponding function for the proposed new try? would -- apart from providing no obvious advantage over the one we just defined -- have to be implemented as two overloaded functions and it would not be trivial to fully document its behavior in a way that isn't easily misunderstood or forgotten.

I haven't cared about source compatibility issues, rather about complex implementation and a system that is hard to reason about, with too many complicated rules and exceptions, and I said:


It does work (as in compile) but perhaps it doesn't work according to the intuition of the programmer. To me this is just another related problem that manifests itself through the current try? operator:

func throwingFoo() throws -> Any {
    return 123
}
let r = (try? throwingFoo()) == nil // This works as you would expect
let r = try? throwingFoo() == nil // Warning: Comparing non-optional value of type 'Any' to nil always returns false

Sorry about that, I noticed that earlier and then forgot about it when someone wrote a new post.

+1 to fix double-optionals resulting from try?.

SE-0230 has been accepted. You can find the announcement here; I will be closing this thread.