SE-0345: `if let` shorthand for shadowing an existing optional variable

I mean, I just spent a whole post spelling out precisely why I do find this to be less clear and expect others to as well, and other posters have corroborated the same point—and in this I was only repeating a point made years ago by me and others.

So either all of us are not credible, or we have long ago satisfied the challenge here; if the conversation in this proposal review of already dubious value is descending to the point of “I just don’t believe you,” then I don’t know what we’re doing here.

14 Likes

What materially changes in those examples if i use a different name?!

      if let v = emailAddress {
        Text(v)
      }

it is still unwrapping, just no longer shadowing. Character count it is shorter than:

      if let emailAddress {
        Text(emailAddress)
      }

and thus easier to "scan", so are we even sure it is "a shorthand" we are discussing here? In my code this form is also quite frequently used case. Do we need to sugar that further because it is frequent?!

I am totally not opposed to shadowing... I am very pro shadowing. Just want us be more conscious about it, making the shadowing intent explicit and have it explicit not just in one particular corner case. What's wrong about making it explicit? Doesn't even require a new keyword if we reuse "override".

Indeed looks like there is no consensus here and it is time to wrap up.

1 Like

I agree that the proposed change probably won’t cause everlasting confusion but I think if let foo = foo has one thing going for it: it’s fully “inspectable” as long as you know how to inspect types via “Help” in Playgrounds or via Option-clicking in Xcode:

3 Likes

I believe in the proposal thread there was some code review done that showed if let x=x was something like 2 or more orders of magnitude more frequently seen in code bases than if var x=x. So at a practical level I believe what we should be laser focused on is “what is the simplest clearest idiom for the most used case?” If the overwhelmingly dominant use case is using an immutable version of a variable in a scope that is conditional on that value existing what about spelling it that way:

if exists{
              [someLongVariableName],
              [someOtherLongVaruableName] in
               …
             }

exists could be valueExists or insertBikeShedHere but the idea is that the conditional executes if the variables in the closure have a value. Then let the compiler make a determination whether to do a copy or borrow.

Personally, I would be happy enough if the feature is explained as a pure shadowing shorthand orthogonal to unwrapping, with the inclusion of the shadowing keyword and the possibility of omitting it in the context of shadowing unwrapping, if one desires.

This would:

  1. Allow the feature to stand on its own and be explained on its own
  2. Allow the use of the feature in other contexts
  3. Allow the use of brevity and omit the keyword when it makes sense, just like self.
  4. Allow the use of clarity and be explicit with the keyword when it makes sense, just like self.

I reckon this approach is the one that ticks all boxes.

+1.

I've been following this proposal for a while, and I've come to the conclusion that this would be a nice improvement to the language. This syntax helps eliminate one of the most common sources of redundancy within Swift, improving the clarity and readability of code. The syntax is easy to learn and remember (if let foo is just short for if let foo = foo), making it easy to use.

One of the things I've seen many people in this thread get hung up on is the fact that if let foo might not make much sense to a beginner. While I think this is a valid concern, it's important to remember that if let foo = foo is already confusing to some beginners. And even if if let foo syntax confuses a beginner, it's not likely to be confused with something else, so there isn't a high chance of misunderstanding code because of this change. It might be worth looking into changing this syntax for Swift 6, but I feel if let foo is the right syntax for Swift 5.

I dislike the idea of requiring a question mark (as in if let foo?), as it would just cause confusion. Why does if let foo? require a question mark while if let foo = foo doesn't? Why isn’t if case let foo? allowed to elide its initializer? Can we use multiple question marks to unwrap multiple levels of optionality? If so, why can't if let foo = bar be used to unwrap multiple levels of optionality? Proponents of if let foo? sometimes point out that this syntax is similar to pattern matching syntax. However, I don't think duplicating that syntax is desirable in this context. Optional unwrapping conditions and pattern matching conditions are different — deliberately so — and the two syntaxes should be distinct to avoid confusion.

I also dislike the idea of creating a new keyword for this operation. Adding a keyword like shadowing or unwrap would just add unnecessary clutter. This community is prone to becoming overly concerned about changes and demanding new keywords/attributes to make these changes as clear as possible. However, it often becomes evident afterwards that these concerns are unfounded and that a feature is just fine on its own. if let foo isn't going to break the language. It might make the learning curve a tiny bit steeper, but it's worth it because it gives us the ability to write less redundant code. if let syntax is hardly the most difficult thing to learn in regard to optionals.

A lot of the criticism of this proposal is based on the concern that new users might be confused by the syntax. However, with Swift being a serious language used by professional programmers, I think it's also worth considering the perspective of advanced users as well. The fact that the language regularly requires the programmer to type someIdentifier = someIdentifier is ridiculous and reduces the clarity of code through its redundancy. Plenty of programmers who don't regularly visit this forum have been clamoring for this feature. Yes, this construct adds some complexity to the language. But Swift is built on the philosophy of progressive disclosure of complexity, not the philosophy of avoiding complexity at all costs.

I agree with the decision not to support if let foo.bar. I think this would be more appropriate for a "member-binding" pattern syntax (aka destructuring syntax, previously discussed here).


Response to @sstahurski

What historical knowledge is required? If you're referring to how if let foo is short for if let foo = foo, remember that syntax is still necessary to write statements like if let firstUser = users.first. The syntax for if let foo = bar is not historical.

Those declarations aren't equivalent though. if let variable = variable, variable would be equivalent to if let variable, variable and if let variable would be equivalent to if let variable = variable.

I'm not sure I'm understanding you correctly. Are you proposing adding this to the language?

postfix operator +
postfix func + (condition: Bool?) -> Bool {
    return condition ?? false
}

I don't see how the + operator makes sense for this operation. + in Swift usually refers to addition and isn't associated with optional unwrapping or booleans at all. Additionally, I don't think ?? false is common enough to warrant a new operator.

Response to @intswift

The proposal examines if unwrap x in its "Alternatives Considered" section and comes to the conclusion that this syntax is less expressive and less explicit than if let x. Do you have any response to what the proposal says?

This response uses a different definition of "exists" from the original claim. @Vinicius_Vendramini is using the word "exists" to mean that an optional variable has a wrapped value. You're using the word "exists" to mean that a variable is defined.

Likewise, you could argue that there is nothing in if let x = x that suggests x will be unwrapped.

I'd argue that

let foo = Optional(42)
if let foo {
    ...
}

is just as clear about shadowing foo as

let foo = Optional(42)
do {
    let foo = 42
    ...
}

. Swift consistently uses let foo to mean "define a new constant named 'foo'", and this new proposal doesn't break that pattern.

Response to @xwu

I don't see why we would need a shorthand for all shadowing. Unwrap-and-shadow is an extremely common pattern, while other kinds of shadowing are much less common and more likely to be confusing.

18 Likes

Now that's an interesting post from the archives, thanks for the link!

3 Likes

Absolutely. I don’t know the inner workings of Xcode’s tooling, and what is and isn’t possible, but it would be really useful if when you inspected a symbol it showed if it was shadowing another symbol.

That would make the shorthand if let foo fully inspectable, but it would also be helpful in other shadowing cases like a local variable shadowing a type member.

While I don't think that shadowing is a problematic enough issue for developers to warrant introducing a new keyword, as @Paulo_Faria proposes, I think tool support that makes it easy to see if a given identifier is shadowing another would be a very useful and helpful capability.

4 Likes

The reason I don't like tool-based solutions is that it is inconsistent. You may or may not get the assist. What about vim users? What about vscode users? What about people reviewing code on GitHub, GitLab, etc? Should all of these different environments have to reimplement tooling?

Some are not happy about the keyword, but IMO that is the appropriate price to pay. You can't have your cake and eat it too.

You mentioned self. upthread. Personally, I've been bit before by implicit shadowing and in complex passages of code with a lot of local state, I make sure to use self. to really be sure I'm accessing what I want, instead of a local symbol. Maybe some developers are above me and can flawlessly keep track of what symbols are valid in all scopes all the time. I prefer to not make things harder for myself. The keyword makes my life easier, not harder. When writing, I have autocomplete. When reading, I know exactly what is going on without having to keep track of things on my head.

4 Likes

Yes, it's definitely a tradeoff. I was responding to someone talking about Xcode, but it struck me that tool support would be helpful with or without a language change.

It sounds like the next step for this would be to flesh it out and start an evolution pitch thread.

1 Like

-1.

I do not think the barrier of being a significant enough value to warrant a change in Swift has been met within this SE or review thread, considering this is a commonly rejected change with a half decade of discussion.

I suspect this can be seen by a large percentage of reviewers still proposing alternative syntax - the reason why if let foo is the syntax proposal set forth is, as far as I can tell from this review, that there's an implementation and that is is likely the most commonly rejected pitch out of the collection of syntaxes.

Independent of the technical merits of the proposal, this appears to be a gap in the evolution process. Presumably if I did additional background research (both through six years of forum discussions on this commonly rejected proposal including the backing pitch) I might see a core team decision on what changes have removed this topic from being a commonly rejected proposal. But that level of research absolutely should not be a requirement on the part of someone offering a review.

As such, there is nowhere near ample information background to actually create a robust review. But I will attempt to proceed with a review - with the mindset that I should (to the best of my ability) ignore years of backing discussion and decisions as if this was a brand new idea.

Which I still would evaluate as a -1.

One of the reasons that this is commonly rejected has been that it does not enable a developer to do anything they were not able to do before. The "problem" as stated in the proposal is one of perceived verbosity and repetition with some coding styles.

On the flip side, this creates problems with understandably. I can no longer personally speak in the context of learning Swift; instead I can speak to my own expectations as an experienced Swift developer. I imagine my team will quickly get used to an if let foo syntax, but if var foo syntax and while let foo syntax seem like they will cause problems if adopted. My opinion of this proposal would increase if the 'if let' and 'guard let' were the only syntaxes being proposed.

From evaluation of my code, 'if let foo = foo' tends to be seldomly used. I typically will get a value through a property or method invocation inline in the if statement, which means that I will still need to declare the (non shadowed) variable that I am binding to.

The cases where I would use this are most typically ones where the conflict is in the locally named parameters of a method - since I already chose internal and external names for an optional parameter, it is unlikely I would choose a third name to avoid shadowing.

The timing seems off for this proposal. There is a section talking about future borrowing syntax. Why would this be adopted independent from such syntax? Such syntactical additions might serve as motivation to revisit a commonly rejected proposal.

There are other languages with different properties and different overall syntax which would compare favorably to this. For example, some languages will do type narrowing on comparison against null, but do so in a way that affects the underlying type in a less impactful way and does not result in variable shadowing.

The closest comparison I could make would be to the option type in Rust, where swift already has significant syntactic shorthands over the rust mechanism of doing a pattern match. This adds yet another shorthand syntax over a pattern matching.

It is possibly worth noting as well the the main reason for having if let or pattern matching in general, as opposed to a functional method-based system akin to the map call, is the need to propagate effects such as loop flow control and exceptions. Although there were some early proposals (such as around property wrappers) to expose effects further to Swift developers, I no longer believe that this is a planned evaluation path.

A bit over six years, off and on.

5 Likes

I could make @xwu’s words my words as well.

Thank you for your great explanation of why we don’t need a solution to a “not a problem at all” thing.

I also think that changes for the sake of changes do not aggregate real good value to the language and our programming day to day tasks.

Too much sugar is too much poison. We have other and more important things to create, discuss more carefully, and then implement.

-Van

4 Likes

Please note that when you consider realistic full examples the feature proposed would also be ridiculous and redundant compared to a version that doesn't use this feature:

if let someIdentifier {
    someIdentifier.baz(someIdentifier.foo + someIdentifier.bar)
}
vs
if let v = someIdentifier {
    v.baz(v.foo + v.bar)
}

Also note that exactly the same could be said about the usage of this feature outside of "if" (was this allowed):

do {
    let someIdentifier // aka let someIdentifier = someIdentifier
    someIdentifier.baz(someIdentifier.foo + someIdentifier.bar)
}
vs
do {
    let v = someIdentifier
    v.baz(v.foo + v.bar)
}
2 Likes

I think most people agree on not using single letter variables.

Length of typing is not a problem. typing multiple words that should be the same is the problem as it is a place mistakes can be introduced.

3 Likes

Make it two letters ("id") or thee... - that's not my point.

(Besides if the function / block is short (as it should be!) there is no confusion with single letter (or short) identifiers, be it "x" or "i" or as in the above case "v", or "vc", etc.)

1 Like

In a world of copy+paste, I find that argument less than compelling. A typo on the LHS of an if let x = x expression also cannot lead to logic errors, so it's not all that critical of a mistake to change the language for.

1 Like

Regardless of the length of the function or block, using a single character (or abbreviated) identifier requires you to look elsewhere for the full description of what the value represents. The meaning should always be clear at the point of use.

I’d argue that the fact that people are unwrapping optionals into variables with less descriptive or even meaningless identifiers is one of the most compelling arguments in favor of the overall goal of this proposal, even if we don’t all agree with every detail of how this proposal currently accomplishes that goal.

2 Likes

That's the very thing I am disagreeing with, as it would be absolutely useless for me to write a "shorter version" of:

if let someIdentifier {

to continue repeating that (long) name a few times within the block.

And on this, as well, the consensus is - that there is no consensus.

I wanted to add this to my review from Swift.org - API Design Guidelines

  • Clarity at the point of use is your most important goal. Code is read far more than it is written.
  • Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters. Brevity in Swift code, where it occurs, is a side-effect of the strong type system and features that naturally reduce boilerplate.

:wood: :fire:

6 Likes

This guidance has come up a few times in this thread. It should be noted that these are API Design Guidelines not language design guidelines. Those things are closely related, but they are not the same.

There is an implied "other things being equal" in the phrase "Clarity is more important than brevity." They are not opposite ends of a one-dimensional slider, but are rather dependent variables. Excess brevity can hurt clarity, but lack of brevity (i.e. verbosity) hurts clarity.

Note that the language is mentioned, at the end of the second bullet:

What is proposed here is one of those boilerplate-reducing features. Clearly this is subjective: one person's clarity-defeating boilerplate is another's clarity-enhancing clarification. But quoting this passage shouldn't be taken as the "case closed, brevity is out" that it's often cited to confirm.

20 Likes