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

-1 for if let x, because I think the syntax is neither obvious in meaning nor sufficient to be an idiomatic standard phrase.

I would argue the opposite: x exists even if it happens to be nil, so if let x should always be true. The test is not whether the variable exists, it's whether it is not nil. There is nothing in the statement that suggests "if x is an optional and not nil then unwrap and shadow it".

In particular, and perhaps most importantly, there is nothing in if let x to indicate that anything is being shadowed. This shadowing is the primary function of the expression, and so I think it is vital that any sugar expresses it either through its syntax, as if let x = x does idiomatically, or through an operator or keyword that will carry the meaning of shadowing.

8 Likes

Here's another thing I just randomly ran into on this page, which should probably be removed now that we actually have a proposal being reviewed on it, or when this gets accepted at latest:

9 Likes

What is your evaluation of the proposal?

Strong +1 as written.

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

Yes, I think so.

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

Yes. All users already have to learn if let x = x { since it will be in codebases everywhere. This simplifies that syntax.

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

No

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Followed most of the (long) pitch thread and read the proposal over.

Read pitch, read review, read this thread up to this point. Of all the other spellings I definitely much prefer the spelling in this proposal. To me it fits best with the direction of Swift thus far.

2 Likes

I'm +1 on this, looks like "magic" but unoffensive enough (and useful enough) that it can be tolerated, I think.

But I'l like to consider an alternative spelling. I think that while the language in the proposal makes it look like we're eliding the = x part, it seems to me that what it's being sugared out is the let x part, that is, when

if let foo = foo {

is turned into

if let foo {

it's actually the first foo that's being removed because it's redundant: you can see that it's the case because the foo symbol in the new form refers to the existing foo in scope. This can also be noticed in the Interaction with future borrow introducers section in Future directions: the

if inout foo = &foo {

is sugared into

if inout &foo {

so the rightmost foo is the one that needs to be preserved for this to work.

This suggests to me that what we should do is probably to elide the leftmost foo, and how? In the usual way we ignore something in Swift, with _:

if let _ = foo {

This would be source breaking, and would require a few more keystrokes, but I think it's a better way to think about this shorthand syntax. I'm fine with the proposed solution as it is right now, but maybe it could be useful to add to the proposal this small clarification, because I think that a better mental model, and learning path, to understand how to go from

if let foo = foo {

to

if let foo {

is to consider the "hidden" intermediate step

if let _ = foo {

I agree this is the usual spelling to ignore something — and therefor, it is really not suited here (imo).
let _ = already exists, and it means "I won't ever use the result of the assignment"...

10 Likes

-1 as it stands today.

From what I understand, the advantage of a shadowing unwrap is not about fewer keystrokes, as others have mentioned. The advantage is not having to repeat oneself. For that, I think adding a keyword makes the operation clearer and still doesn't force us to repeat ourselves. So I like the proposal:

if shadowing let someOptional {
  ...
}

The keyword shadowing acts like the mutating and nonmutating function and accessor modifiers, but for the let (and eventually var, inout, etc) declaration.

if shadowing inout &someOptional {
  ...
}

Yes, it is more verbose, but we don't want to fix "verboseness", we want to fix repetition, and we also want to be clear. Look what the name of the proposal and then look at the proposal above. I don't think it can be more straightforward than that, without making it confusing. Code is read much more than it is written. With the shadowing modifier, we have autocomplete, so we don't suffer much writing, and we benefit a lot when reading. In my opinion, this is the most balanced proposal.

3 Likes

Also, this modifier can be used to shadow a symbol, changing its mutability, for example.

Instead of:

func foo(bar: String) {
  // bar is immutable here
  var bar = bar
  // bar is shadowed and mutable here
}

We do:

func foo(bar: String) {
  // bar is immutable here
  shadowing var bar
  // bar is shadowed and mutable here
}

Again, shadowing var bar is more verbose here, but we don't want to optimize "verboseness", we want to optimize the repetition inherent to shadowing. We also want to make clear why we are shadowing. In this case we are shadowing it to change the mutability of the symbol.

This feature is straightforward to understand and easier to explain, since it has clear grammar associated to it. Swift has been criticized before because of the amount of keywords it has. I think it does a pretty good job in making things clear, and if that requires extra keywords to convey meaning, well, it is what it is. Complex things require jargon and specialized words. It's better to understand the problem and use the appropriate language to deal with the issue.

The title of the proposal talks about optionals, but as others mentioned before, the issue is really about shadowing, and it applies to other instances of shadowing as well, as I just showed. The key issue is the repetition caused by shadowing and that's what we want to fix.

1 Like

I know we had this in the very first versions of swift and got rid of, but personally I don't see a problem of having "var" in the function parameter position. To allow for not so rare case of mutable parameter and to make it compatible with "for x ..." / "for var x ...".

func foo(bar: String) { // bar is constant
func foo(bar: inout String) {
func foo(var bar: String) { //  bar is mutable as if "var bar = bar" was called

func foo(var bar: inout String) { wouldn't make sense.

My evaluation is this:

I am baffled as to the grounds on which a commonly rejected proposal has made it to the review stage without any substantive change in the arguments for or against it since the core team originally decided that the feature was to be listed as commonly rejected. No shortage of discussion has been had about this feature, I have followed every message, and I am not aware of any (on-topic) reply in this particular iteration of the pitch or proposal stage which has not been said before.

The feature itself would, if it had been added in Swift to begin with, been unremarkable and I doubt would cause any trouble. However, it is yet another shorthand to learn in a nested set of shorthands for a very common operation, and the bar for adding it now is (or, should be) that it solves some actual problem.

Repeating oneself does not in and of itself constitute a problem. Indeed I have said before, and I will say again, that the repetition in if let x = x is load-bearing, because it is the only part of the spelling that indicates shadowing of the variable in the outer scope that is not itself mutated. In that light, it is not some extraneous syntax. The point is well taken that, in a capture list, [x = x] and [x] are equivalent, but this is not a sufficient rebuttal for two reasons: (1) closure syntax is deliberately and uniquely in Swift endowed with a nested set of shorthands (deliberately, by design) not allowed anywhere else, such as omission of argument types or even parens around the argument list; (2) leaving aside the first point, one would have to be convinced that the equivalence of [x = x] and [x] is user-friendly enough to be something we would want to use as precedent for Swift syntax is other contexts—a notion that is far from self-evident.

Now, even if repeating oneself is not a problem across the board, it remains open to argumentation that repeating oneself in a specific context is problematic enough to be a problem worth solving with new syntactic sugar. Indeed, it has been attempted to construct such a scenario, more or less along these lines: First, suppose the variable name in question that we do not want to repeat is long, since it wouldn’t be nearly as burdensome to repeat a short variable like x. Then, suppose that it is important that we shadow the variable without shortening its name one iota, because spelling out the variable’s full name is essential to clarity of the code when the variable is referenced in the inner scope. Yet, consider how it is burdensome to spell out the variable’s full name twice in order to unwrap and shadow the optional. I am doubtful that there exists such a “Goldilocks” variable name where uttering it n times (once when unwrapping, then n – 1 times inside the inner scope actually using the variable) is essential, but uttering it n + 1 times (twice when unwrapping instead of once, then n – 1 times inside the inner scope) is too burdensome—let alone enough of these to be a problem that can justify an addition to the language.

If there was any plausible issue here in need of solving, it was that there’s nonetheless a mechanical burden to typing out a variable name twice and making sure that the spelling isn’t flubbed for the first one (the unwrapped binding) without the help of autocomplete. But in the time since this syntactic sugar was first floated, Xcode has implemented autocomplete for if let unwrapping, demonstrating that tools-level support can and does improve the ergonomics of the language without baking in new syntax.

To summarize: My overall evaluation is that the proposed syntax would be inoffensive if it already existed but cannot be said to address any problem significant enough to warrant an addition to Swift. Additionally, the precedent it sets that already rejected ideas can be brought up years later to be adopted without any substantive change is itself severely damaging to the coherence of the processes in place. As such it is out of place with the overall direction of Swift—direction, after all, being singular. I have not seen that any other language (and in particular I am thinking of Rust which was inspired by Swift’s if let) has adopted further shorthands for unwrapping optionals. I have been following this discussion in depth for years.

48 Likes

I mostly agree with @xwu. However, I don't think it is "innoffensive". I think the proposal, as is, makes the language harder to learn/explain. One of the tent poles of Swift is clarity. Personally, I don't think this is a problem worth solving, but the constant rehashing of this topic makes it clear that a substantial number of developers are greatly annoyed from the lack of such construct. Maybe this is what prompted the core team to consider dealing with this issue.

Meta-discussions aside, if this really is something worth solving, adding the shadowing keyword is what optimally solves the issue, making it clear a shadowing is going on and also avoiding the dreaded repetition, not only in the context of optionals, but in all instances of shadowing.

7 Likes

Autocomplete helps during writing of course but it doesn't solve the issue of increased brain load required to parse the expression to see if it's a match or a non match during (a much more often) reading:

if let yourSurname = yourSurname { // shadowing
}

if let yourSurName = yourSurname { // deliberate name change (e.g. to avoid shadowing or to confuse the reader)
}

if let yourSurnome = yourSurname { // a typo, or a deliberate spoofing
}

if let yĐžurSurname = yourSurname { // homograph spoofing ("Đž" vs "o")
}

with a shorthand notation "if let yourSurname {" no additional mental parsing is needed, all is clear straight away.

Thinking this argument out loud makes me change my original +0.5 to a full +1 for this feature.

Edit: Further thinking about "override let x" / "override let x = 123" alternative caused my vote change down to -1 on the current proposal. Details below.

9 Likes

The proposed syntax does not really improve the language. It even causes confusion because it breaks the grammar rule for variable declarations.

These days, we are no longer punching cards; retyping a long name is not really a problem. Just copy and paste. :slight_smile:

However, the following could be a good comprise (it takes only two extra characters and does not break the grammar). '.' is short for optional bar.

func foo (bar : Int?) {
     // conditionally bind to bar and shadow it
    if let bar = . {
       ...
    }
}
1 Like

(a) If that argument is sufficiently convincing for you as to merit an addition to the language, then it argues for a proposal that covers all shadowing and not just in the case of if let unwrapping (not to mention that you’d want a shorthand that indicates that something was shadowed rather than entirely omitting any written artifact of the operation)—which this proposal does not do, nor even lists as an alternative, even though I raised the point many years ago and others are raising it even now.

(b) Continuing on the theme of tools-level support, this is an excellent example of something that can be addressed by syntax highlighting.

Again, this entire line of argumentation has been discussed before too, and I don’t think anything I’ve said nor anything I’ve replied to has been unmentioned years ago already and many times since, illustrating again the overarching point that I fail to see what has merited review now nor what is accomplished by it.

8 Likes

Could it be that the problem is not a linguistic/syntactical one, but a problem with the Optional type itself?

On the face of it, Optional is very simple: just an enum representing the presence or not of a value. How hard can it be? However, several years of Swift has shown that a lot of frictions arise, of which unwrap-and-shadow is one. Other difficulties are nested optionals; unwrapping once or completely flattening; does or should if let x = array.first fail if the first item in the array is a nil-optional; the problems of CGFloat/Double conversion with optionals; and so on. This is also intertwined with awkwardness around weak references and thus onto perennial issues such as [weak self].

It seems to me that these frictions are due to a mismatch between the agnostic nature of the Optional type and its indifference to the situations in which it is used.

In this particular topic, why are we having to unwrap-and-shadow an optional? Is it because a lack of a variable carries meaning? Is it because we want to cleanly represent an invalid value (e.g. using Double? = nil instead of Double = NaN)? Is it to break a retain cycle, such as [weak x]? Are we just wanting to let a function caller omit an argument for convenience? Something else? All of these things at different times?

Perhaps there is cause to have explicitly different kinds of Optionals for different purposes, each of which is suited to minimising each of the different frictions by their nature instead of syntactical patches at point of use.

Now that so many use-cases with friction are well documented, with attendant recurringly-suggested pitches, is the entire concept of "optionality" (including the related weak references) in need of reconsideration and possible redesign?

FWIW, I think "this sharp corner continues to cause annoyance even for experienced Swift users after N years" can reasonably constitute new information.

I really strongly disagree here. I think it is reasonable to expect that opinions will change over time, especially as the rest of the language changes. Yes, I agree it would be tiresome to constantly re-litigate every item in the common rejections list, but IME that's not happening. The vast majority of the entries on that list have not seen continued traffic, but this specific item continues to have a lot of traction every time it comes up for discussion. IMO it is perfectly healthy, process-wise, to formally reconsider decisions that were made years ago.

26 Likes

I would somewhat agree with you on (a), e.g. this can be "resurrected" at some point in the future:

func foo(var a: Int) {

to be an equivalent of

func foo(a: Int) {
    var a = a

to kill two birds with one stone.

Other shadowing cases are more rare to worry about.

I also like the focus of this proposal: if it tried to be much more to solve several issues at once it would never be positively received.

on (b) - it's not only about Xcode. Stackoverflow, or GitHub web interface pull requests, or many other places where code is plain text... That's where Swift is superior to Kotlin (which only shows argument labels in the IDE).

I disagree. I don't think ad hoc solutions are sustainable on the long run. I prefer solutions with strong foundations that can be further expanded, instead of patchwork facades.

8 Likes

What is your evaluation of the proposal?

-1

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

No. I just don't see this as an issue worth addressing.

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

No. It favors brevity over clarity and offers no good reason for existing. The example is contrived to illustrate two extremes without acknowledging that a middle ground is possible. It is not that hard to use short (which doesn't mean as short as a or b) variable names while still making them ergonomic and descriptive. This is an issue with how devs name their variables, not with how the language deals with them. As such, it doesn't need a language solution.

In addition, I feel that it makes learning Swift just a bit harder.

if let x = x may be repetitive, but it is easily explainable and easily learnable. It is clear to see that a new variable is being assigned the same value as an existing variable; the only part that isn't immediately clear is the unwrapping of the Optional.

if let x elides not only the unwrapping of the Optional but also the assignment from an existing variable. It removes clarity of functionality. My immediate thought is "let x what?" A new Swift learner can, of course, be taught this idiom, but it should be clearer and more like other things they are learning about the language. And where else is an assignment made without indicating just what is being assigned?

Not to mention that a new learner now has to learn two ways of unwrapping an Optional and assigning the value to a new variable, depending on how they want to name the new variable. Yes, this just sugars over the existing grammar and they don't have to use it in their own code, but it will get used by other programmers and so new learners will need to know it when they read other people's code. There is the added cognitive burden when seeing if let x of "what is being assigned here, oh yeah it's shadowing an existing x" that isn't present with the current if let x = x.

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

No opinion.

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

Read the initial pitch thread, the proposal and all the arguments in this thread.

5 Likes

I don’t think we disagree on the underlying principle that where the language changes prior decisions may need to be re-evaluated—this only makes sense, since it would not make much sense to stick to a decision when the premises on which that decision is based no longer apply.

But this is not the case here: that there has been language evolution generally doesn’t mean that the factual basis on which any specific decision was made have changed. And there can be no better demonstration of that than when the pitch and proposal for this iteration of the feature in question garnered numerous replies, essentially all of which repeated exactly the same conversation that has already been had many times prior.

This is unhealthy because it is a diversion of everyone’s limited bandwidth to revisit an already amply discussed topic with demonstrably no additional arguments for or against surfaced. It devalues and demeans prior contributions and discourages participants from contributing their best because, why bother when the same thing will come round next season? A large part of the rationale for moving to these forums was so that messages are as easily reachable and searchable five days or five years from now, yet this particular proposal process has paid no mind to those contributions: in the midst of the pitch phase, contributors were block quoting their past messages into the ongoing thread. That is a job that a bot could do, not worthy of the attention of this many minds.

That there are topics that garner repeated attention is why we have a commonly proposed/rejected document to begin with. Arguably, the other topics which are no longer commonly proposed after prior rejection can be removed from that document because they are no longer commonly proposed. The document serves no purpose at all if it records only past debates that have no traction today.

14 Likes

-1 for now
If let foo = foo { } is inconvenient. But new if let foo { } is confusing, there are a lot of arguments above, no reasons to rewrite them.

It would be nice to make it more clear, like:
if let unwrap foo { }
if let bind foo { }
if foo != nil { print(foo) } – foo is not optional inside local scope because we checked that it is not nil
if can let foo { }
if let in foo { }
if let by foo { }
if case let foo { } – the shortened variant of current if case .some(let foo) = foo { }
if let foo? { }
if let ? foo { }

For my own, I would prefer these variants:
if let foo?, let bar? { }
if let ? foo, let ? bar, { }
if case let foo, case let bar { }