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


(Chéyo Jiménez) #3

+1
This problem is significant.
Fits well with the language. It will remove some pain points.
The nesting optionality of Swift is something I have only used in swift. It is a weird feature that in my experience has only given me problems. I do not recount a single instance where in my code I ever wanted nested optionals. Dictionaries is another area where nested optionals are not very useful.

I greatly desire a generic level feature where I can specify that I only ever want one level of optionality.

The progressive improvement in this area is something that I welcome. I would like to hear from the core team on any plans for explicitly being able to enforce optionals with no optional nested values.

I read the proposal and followed previous discussions lightly.


(Adrian Zubarev) #4
  • What is your evaluation of the proposal?

I'm okay with this change.

  • Is the problem being addressed significant enough to warrant a change to Swift?

I think we should make this breaking change since it aligns the behavior of keywords that make use of the ? for mapping a type into an optional. As described in the proposal the as? infix operator will technically try to flatten the type as much as possible (it could be achieved by calling flatMap on the optional type, but the concrete implementation might look different). In that sense there is no easy way to express the same flattening behavior for try? except of a manual one if let doubleOptional = try? something, let unwrapped = doubleOptional.flatMap({ $0 }) or a little bit more terse if case let unwrapped?? = something.

  • Does this proposal fit well with the feel and direction of Swift?

I personally say it does.

  • 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?

I quickly read the proposal.


(Rod Brown) #5

What is your evaluation of the proposal?

+0.5
I think consistency between try? and as? is reasonable, and this is a decent solution to convoluted code that can be difficult to read.

Is the problem being addressed significant enough to warrant a change to Swift?

I’m not 100% sure. Does this pass the bar of “actively harmful”? I’m not sure it does, though it is surprising to find such a glaring inconsistency not caused by API but by the language itself. I would put this in a “would be nice” group. Not essential, but welcome.

Does this proposal fit well with the feel and direction of Swift?

Yes, consistency is important in how our operators work, to make Swift an easy to learn and intuitive language.

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.


(BJ Homer) #6

This precise use case is actually addressed in the proposal, but the underlying principle may not be clear. Perhaps this is a better way to explain it:

  • Applying try? to a non-Optional expression of type T wraps the type in Optional<T>.
  • Applying try? to an Optional expression does not change the type of the expression at all.

Or an even simpler formulation:

  • Adding try? makes an expression Optional, if it was not already Optional.

So applying try? to something that is already Int?? would not change the type at all. It would remain Int??.


(Adrian Zubarev) #7

I might have overlooked it, but the behavior wouldn‘t be consistent that way.

let a = trippleOptional() as? T // will flatten from T??? to T?

let b = try? trippleOptionalWithError() // will flatten only once from T???? to T???

type(of: a) != type(of: b)

(John McCall) #8

Casting doesn't really "flatten"; it looks for any way in which it can interpret the operand value as the target type, and then wraps in a level of optionality to represent that that search can fail. But it'll happily look through protocol types and so on. Any static behavior is going to seem inconsistent compared to that if you think of it as flattening, including the current behavior of try?.


(BJ Homer) #9

The proposed behavior of try? is more similar to performing as? <subExpressionType>. So instead of comparing to as? T, it would be more similar to as? T???

func tripleOptional() -> Int??? {}
func throwingTripleOptional() throws -> Int??? {}

let a = tripleOptional() as? Int???   // produces Int???
let b = try? throwingTripleOptional() // produces Int???

(Chéyo Jiménez) #10

This actually produces four levels in 4.2

I am totally confused about the return on investment here. This is not what I understood from the proposal. Either the proposal needs to make this more clear or the name needs to change away from calling this a ‘flatten’ behavior. Shaving only one level off will make this addition soooo confusing.

I am leanning towards a more holistic approach instead if that is the case.


(Goffredo Marocchi) #11

I am not sure what is the benefit from the user point of view of not flattening always to a single optional... Int????? does fill the abstract rules we have today, but from a user point of view having a single level of optionality max makes most sense. The language and tooling should make it possible to stay always at the Int? level and avoid Int?? or Int??? at least from the user visible point of view (regardless of the internal behaviour / machinery).


(Tino) #12

As far as I understand it, it's more or less the same what optional chaining does (but something completely different than as? - I think the proposal is just plain wrong in this respect).
Full (or maybe it would be better called "maximal") flattening would be without precedent, and imho it would make everything even more complex.
I tried hard to start a discussion about the general utility of nested Optionals in the original thread, but I guess debates about issues that won't be addressed aren't that popular ;-)


(Goffredo Marocchi) #13

Nested optionals seem to me as an implementation detail leaked out to users. What is the advantage of user level code thinking about Int?? vs Int?... something that could contain something that could contain something...


(Roy Hsu) #14

What is your evaluation of the proposal?

+1

Is the problem being addressed significant enough to warrant a change to Swift?

I never find any use case I really need Int???? over than a simple Int? from the daily development.
The current behavior of try? makes optional chaining less intuitive than it should be.

Does this proposal fit well with the feel and direction of Swift?

I totally agree that nested optionals are difficult for users to reason about when using try?.
The proposed solution does improve the experience and make the language much easier to learn.

So I think it fits the direction of Swift.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Quickly read the proposal.

Edit: Modify my review that may confuse the goal of this proposal.


#15

I wouldn't say a flat "wrong", but I agree that comparing try? with as?, when done too fast, can bring confusion.

As @John_McCall says above, as? is of its own kind.

The proposal is not, in all rigor, about bringing try? closer to as? on optional depth. But it does indeed, in practice, almost by chance, bring try? closer to as? on the number of concrete types actually found in a given program.

The real motivation, for me, is that the Swift language actively avoids adding levels of optional nesting unless explicitly required by the user. T?? exists, but we all know it is confusing, so you have to ask for it.


(Pedro José Pereira Vieito) #16
  • What is your evaluation of the proposal?

+1

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes, it is a significant problem.

  • Does this proposal fit well with the feel and direction of Swift?

Yes.

  • 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?

Read the proposal and previous comments.


(BJ Homer) #17

… Huh. That's true. Okay, I agree that optional chaining is a better model for what is proposed here than as?.

The point of the proposal is to avoid avoid adding any additional levels of Optional-ness when using try?, other than the minimum required. If your type system already has nested Optionals for some other reason, try? should not be the tool for removing them.

This proposal just enables us to avoid creating a nested Optional unless the user explicitly requests it.


(Chéyo Jiménez) #18

We appreaciate the work that went into this. I just read this sentence three times. I still interpret this a single level flattering. I know that that is not what you’ve meant.

Other people seem to also think of this proposal as total flattening.

I’ve realize that even saying something like “single level flattening” is ambiguous. What does that mean? Taking a level off if there are more than one? Or does it mean alwayss producing a single level of optionality?

I do not believe people are reviewing the same proposal. I would kindly ask for the proposal to be sent back to revision.


(Soroush Khanlou) #19

+1.

Yes. I remember the first time I ran into this rough edge, and thinking that it was definitely the incorrect choice. I think the particular case dealt with optional chaining a throwing function: try? someOptionalValue?.someThrowingFuction() returns a double optional. I was confused when it caused my code not to compile, and when I figured out what was going on, I was upset — of course I didn't want a double optional! Why would I want a double optional? I'm trying to elide the error, not still have to deal with it.

Yes. As the proposal notes, it's similar to how as? works. It was quite a while into my Swift experience where I realized that as? handles both optional and non-optional values on the LHS, and I remember being pleasantly surprised at how the feature does the right thing and I didn't have to think about it. try? currently doesn't have that delight.

Only slightly related: Kotlin has optional chaining that doesn't consume the entire rest of the expression, only the next method call. Where in Swift you might have let result = optionalValue?.nonOptional.nonOptional.nonOptional, in Kotlin, you have to insert intermediate ? for each method call: let result = optional?.nonOptional?.nonOptional?.nonOptional. It's not exactly the same situation, but I do think it shows that when doing the exact technically right thing (Kotlin's behavior here) feels totally wrong in most situations, you should do thing that meets the users of the language where they are.

Quick reading.


(Roy Hsu) #20

No, I don't think the goal of this proposal is to totally flatten optionals.
That's just my personal preference to prefer single level optional operation.
If there was really a use case that nested optionals make sense. I'm fine to have them.
So it shouldn't be the reason to block the main idea of this proposal.

I edited my review to bring the focus back to the original idea of this proposal.


(Brent Royal-Gordon) #21
  • What is your evaluation of the proposal?

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?

Quick reading.


(Tim Vermeulen) #22
  • What is your evaluation of the proposal?

+1. I think it's unfortunate that this is addressed so late, but Swift will be a better language for it by not having

try? JSONSerialization.jsonObject(with: data) as? [String: Any]

produce a double optional.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes. try?'s current behaviour needlessly causes confusion for many developers. A common way people currently work around this problem is with

(try? JSONSerialization.jsonObject(with: data)) as? [String: Any]

which won't break, so I don't expect a lot of code to break because of this change.

  • Does this proposal fit well with the feel and direction of Swift?

Yes. Like Brent said, this is consistent with how optional chaining works.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal and followed the earlier discussions.