I'm a little surprised we're considering this at all since it's a source compatibility break. But assuming we've cleared that hurdle, I think it's probably a good idea. While it's less pedantically correct, I think users will find it more useful in practice. try?is a convenience feature—it ought to be convenient.
Is the problem being addressed significant enough to warrant a change to Swift?
It does cause much confusion to some users, and the source compatibility suite analysis suggests that most users are working around the current behavior, not using it. So I'd say yes.
Does this proposal fit well with the feel and direction of Swift?
Yes. This change matches optional chaining's behavior.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A.
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Is the problem being addressed significant enough to warrant a change to Swift?
Absolutely. The existing behavior is highly unintuitive and causes a break in development flow even for experienced Swift programmers. I've run into the issue this proposal addresses many times myself and never once have I hit a case where the existing behavior was desirable versus the proposed behavior.
Does this proposal fit well with the feel and direction of Swift?
Very much so.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A
How much effort did you put into your review? A glance, a quick reading, or an in-depth study
Somewhere between a quick reading and an in-depth study.
Is the problem being addressed significant enough to warrant a change to Swift?
Yes. I have encountered this several times, and it’s always painful to work around it, resulting in distracting extra ‘boilerplate’ code.
Does this proposal fit well with the feel and direction of Swift?
Yes. It’s pragmatic and ergonomic, and fits the general pleasantness of Swift.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Quick reading after following the previous discussion.
I was against this proposal until I realized that as? casting exhibits flattening behavior. Now I'm strongly for it. It solves a very unfortunate ergonomics issue and I see no problems with the proposed solution.
From the source compatibility analysis, it seems like (try? getXYZ()) as? XYZ is the simplest workaround people have discovered and the proposed solution is basically compiler sugar for exactly that. It's simple enough to describe and should quickly become second nature.
Is the problem being addressed significant enough to warrant a change to Swift?
The issue is minor, but it sounds like the impact will be minor (and mostly positive to me) based on the compatibility analysis. Making the type system more ergonomic can only help to persuade users of typeless languages to consider using Swift — and that is a big deal for we backend developers.
How much effort did you put into your review?
Read the proposal, discussion, and experimented with some of my own sample code.
Is the problem being addressed significant enough to warrant a change to Swift?
I think it is.
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Quick reading and personal experience with nested optionals.
I honestly still don't know what to think about this proposal, despite participating in the discussion thread and thinking about it extensively. On the one hand, this makes the behaviour of try? significantly more complicated (as seen several times in this very review thread) and I would generally prefer simpler rules with a little more boilerplate. On the other hand, the behaviour of all these other related language features (e.g. as?) is already similarly complicated and hard to internalise (again, as seen in this review thread), so perhaps making try? slightly more ergonomic in some cases is worthwhile.
To summarise my thinking, the downsides of making this change are breaking source compatibility and complicating the behaviour of try?. The upsides are improving consistency with other related features and avoiding the double optional, which can be surprising and require some extra code to unwrap. I will note that the analysis of the source compatibility suite for source-breaks also double as a rough benefit analysis for this change, and the result is that it possibly helps in about 1-2% of try? uses.
Is the problem being addressed significant enough to warrant a change to Swift?
Even though the proposed solution introduces a source breaking change I think it warrants a significant improvement for all Swift users. After teaching many students the basic of Swift most of them were confused once they encountered this problem the first time as it doesn't behave like as?. Furthermore, I think even experienced users get a great benefit out of this and it improves the language.
Does this proposal fit well with the feel and direction of Swift?
Absolutely
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Followed the discussion thread and read the proposal
I'm mostly for this proposal. I think this breaking change is okay for a couple reasons:
Double optionals are mostly uncommon in natural Swift code that doesn't arise from language semantics.
try? is just a sugar, and having it create double optionals when most of the time the user is going to be unconcerned with the levels of optionality and what they entail is a bit much.
My main hesitations are around changing semantics of a construct this late in the game. I don't think this optional wrapping behavior is actively harmful. While things will get wrapped in double optionals, this behavior is well defined and safe. There's no risk of the user trying to use the value when it could be nil, since Swift still protects you here.
Is the problem being addressed significant enough to warrant a change to Swift?
Yes. This has been the most annoying thing (syntactically) and currently works in a non-intuitive way compared to most other use cases.
Does this proposal fit well with the feel and direction of Swift?
Yes. Making error handling easier and less verbose is good for the future usability of the feature
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Read through it and gave it a few days of thought.
My only suggestion would possibly be to add a compiler warning when returning a func() throws -> \*Type\*? that explains the behavior. Trying to think of how to phrase the warning makes me think there's an underlying issue with the complexity of wrapped optionals as others have pointed out.
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
I think so. I was a bit skeptical on first blush, but two things convinced me:
The research on the source compat suite's non-reliance on the existing behavior is compelling.
The parallel to as? is decisive.
When Swift is at its best, it feels simpler than it truly is because it provides a sort of heuristic consistency that allows developers to reason about disparate features using a unified mental model. (This is closely related to the idea of “progressive disclosure” that Chris Lattner talks about.)
When Swift is at its worst, it feels like a minefield of special cases because it forces developers to understand its subtle formal distinctions in ways that prevent a casual mental model from being effective.
The former should be an ongoing design goal for Swift — and the language should be willing to tolerate some judicious source breakage to support it.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
Swift’s type promotion/conversion conveniences around Optional make it far more pleasant to use than the otherwise nearly identical Maybe types in ML family languages. I second Brent: conveniences should be convenient. And this convenience is one of Swift’s defining features.
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I have updated the proposal slightly to clarify how the proposed change compares to the behavior of as?. In short, because as? takes an explicit type, it can flatten or add multiple levels of optionals if requested. That is not proposed for try? here. In practice, though, as? is often used to flatten two optionals down to one, as in the JSONSerialization example.
The parallel to the behavior of optional chaining (foo?.bar) is unchanged.
Not only that, but 'as?' considers the dynamic type of the left-hand side (and indeed that is the entire point of the operator), whereas the operator here very much does not, as you make clear in the section about generics. For that reason, the comparison with 'as?' here is misleading, in my opinion.
I think this proposal is a good example of one that would benefit from a "How do we teach this?" section that was discussed in another conversation. Here, the claim is made that the proposed change is more intuitive and consistent with the rest of the language than the current state of affairs. A crucial demonstration of that claim would be showing how teachable the new rules are. However, if readers of this proposal approve of it on the misunderstanding that the operator 'try?' will be aligned with the operator 'as?', then we will have done the cause of intuitiveness and teachability rather a disservice.
That said, because the rules proposed here more closely align 'try?' with '?.' chaining, I would have been in favor of them unreservedly if we were still in the Swift 3 (iOS dictation: swiffer three) timeframe.
My major hangup as to this proposal overall is largely the same as those expressed by others, namely that it is source breaking at this late stage, and I am not entirely convinced that the benefits going forward can justify outright source breakage for Swift 5. Because, after all, this all has to do with static typechecking, misunderstandings are largely flagged by the type checker at compile time, preventing such misunderstandings from being actively harmful to the correct behavior of the resulting code.
I see it the other way. To-date, I haven't seen any arguments for the current behavior in terms of logic. Even those who argue that information will be lost if this change happens cannot show any examples where anyone cares about that information. Rather than being too late to make this change, I see this as the last opportunity to make the behavior match what developers actually want from the operator.
Well, as I said here and in the pitch thread, the behaviour of try? is much simpler and (at least for me) easier to understand if it always introduces one level of optionality. And I think the confusion throughout this thread about the behaviour of other similar constructs shows that there might be a benefit to having simple behaviour. That said, I still don't have a strong opinion either way on the proposal. It comes down to a marginal cost-benefit tradeoff, and it seems rarely useful or harmful either way.
I think I agree; the analogy with as? in the proposal is misleading. It was included because of a misunderstanding I had about how as? works when I wrote the proposal. I’ll update the proposal tomorrow to just remove the comparisons to as? entirely (other than those used incidentally, as in JSONSerialization examples).
If the type you are trying to retrieve is not optional, it will become optional because of the optional chaining.
If the type you are trying to retrieve is already optional, it will not become more optional because of the chaining.
Therefore:
If you try to retrieve an Int value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
Similarly, if you try to retrieve an Int? value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
This explanation can be used almost verbatim to describe the proposed behavior for try?. That suggests to me that this proposal is well-aligned with what users might expect as they learn the language.
As to concerns about source compatibility, I would be interested to hear of any cases where this would be a source-breaking change. There do not appear to be any in the compatibility suite. However, the compatibility suite is biased toward libraries rather than full apps, and it may be the case that libraries are more likely to expose error conditions, and thus less likely to use try? in the first place.
In the apps I work on professionally, this change would not be source-breaking; all of our uses of try? remain compatible under this change. But if such breakage is out there, I’d love to know what it looks like so that we can make sure it is migratable if this proposal is accepted, and so that we can get a sense of how much incompatibility exists.