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

What is your evaluation of the proposal?

+1.

Inherently if let x = x is not in my view inherently clearer to a new Swift developer than if let x. As an Objective-C developer learning Swift, encountering if let x = x was initially counter-intuitive since it appears to be assigning a variable to itself. So whichever syntax is used, there is a learning curve to understand it relates to unwrapping an optional, and once learned the shorter version is clearer to read and write.

I am strongly against the alternative proposal of if let x?. In other contexts, a ? attached to a variable name conveys that the variable is optional, such as in if let valueOfInterest = optionalObject?.variable. Extending this, if let x? is essentially arguing to omit the left side of the current syntax:

if let x? reads to me as if let (x =) x?

Rather than the proposal, which in my view much more logically proposes to omit the right side of the current syntax:

if let x reads to me as if let x (= x?)

The impression that the ? syntax gives to me, as a naive reader, would be that I might still be dealing with an optional, not that I have now unwrapped one.

I think the main potential downside to the proposal is that if let x = x is counter-intuitive to the point that it forces a new Swift programmer to learn about optionals and about variable shadowing, whereas it might be possible to think from if let x that you are simply unwrapping the current variable, rather than shadowing it. However, any attempt to use x out of the scope of the closure would immediately give compiler warnings that x was optional, alerting the developer to an error in their understanding. So I don't think this argument against holds much weight on inspection.

Personally, I will particularly enjoy being able to guard let foo else { return }.

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

Yes. Clearer code, removing a small amount of unnecessary duplication.

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

Absolutely.

In contrast to some other respondents, I feel it reads very clearly: if (you will) let (me have) x { ā€¦ } :stuck_out_tongue_closed_eyes:

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

I havenā€™t used such a language.

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

I read the proposal, every reply above, and considered for a bit what I thought as a regular old person writing Swift each day.

7 Likes

What is your evaluation of the proposal?

+1

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

Yes. Once shadowing became popular and shadowing self became possible, this felt like a missing piece for me. Iā€™m biased as Iā€™ve been asking for this for years.

That said, if the change isnā€™t made itā€™s more of an inconvenience than an obstacle. E.g. it would be nice but thereā€™s nothing I can or canā€™t do now that this would enable or prevent.

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

Yes. Things like not requiring return in a single expression function and removing the need to explicitly capture self in a closure at times - although very separate notions feel like a similar direction.

I disagree with others that this will be pedagogically easier for beginners or newcomers to Swift. It will need to be explained but is no harder than other aspects of explaining optional

How much effort did you put into your review?

I have followed the discussion and read the proposal

6 Likes

I have to agree with Daniel on the point of beginners. Not knowing the history of why an engineer has to do this makes this very obfuscated.
Again, I would rather see an operator overload introspectively look an optional.

if variable+ { // if let variable = variable

The simplicity of this speaks for itself, its non verbose, and beginning engineers would understand it immediately.

What is your evaluation of the proposal?

+1 for simplifying the syntax
0 for how it is done

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

Yes!

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

No.
Swifts type system does not force me to specify the type if the compiler can find the correct type (let x = _expression_ just works).
Optionality should work the same.

let x: Foo? = functionThatReturnsOptionalFoo()

// x might be nil here

if x != nil {
   // x is never nil here and the compiler should know that.
   // x != nil is just syntax sugar for "case .some(_) = x"
   // Why not redefine it to something like "case .some(let x: Foo!) = x"?

   x.bar()   // This should work.
   x?.bar()  // This should work, too.
             // Just as let x: Int = 4 does.
}

// x might be nil here

if Bool.random() {
   x!.bar()
   x.bar()  // After all: Why not?
            // Runtime error on x! is an
            // explicitly defined behavior
}

// x might be nil here

guard x != nil else { return }

// x is no longer optional here.

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

Yes.
Sending messages to nil was defined behavior that resulted in unexpected situations.
I prefer the Swift way.

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

Followed the initial discussion but mostly gave up on SE.

This isnā€™t really true. As described in the Alternatives considered section of the proposal, in a switch or case pattern, ? after variable name means precisely "unwrap one level of optionality", i.e. the variable before the ? is not optional:

enum Foo {
case bar
case baz(Int?)
}

switch foo {
case .bar: break
case .baz(nil): break
case .baz(let value?): break // value is Int, not Int?
}
7 Likes

Without passing judgment on whether the proposal is needed, I would like to make an argument I havenā€™t seen for if let x? as the better spelling (apologies if someone has mentioned this already):

In case and switch statements, x? today means "unwrap one level of optionality". In the rare case that you have a nested optional (and they do come up!), being able to unwrap multiple levels of optionality by using multiple question marks can come in handy:

let single: Int? = 0
let double: Int?? = 0

if case let value? = single {
    print(type(of: value)) // Int
}

if case let value? = double {
    print(type(of: value)) // Int?
}

if case let value?? = double {
    print(type(of: value)) // Int
}

By analogy then, if we spell this new feature if let x?, using multiple question marks could allow the user to unwrap multiple levels of optionality:

let single: Int? = 0
let double: Int?? = 0

if let single? {
    print(type(of: single)) // Int
}

if let double? {
    print(type(of: double)) // Int?
}

if let double?? {
    print(type(of: double)) // Int
}

This seems to me both more consistent with the existing pattern matching in the language, and strictly more powerful than the if let x spelling, since that can only ever unwrap a single level of optionality. (if let x = x only unwraps a single level of optionality.)

16 Likes

(Review manager hat on)

You've already proposed this new syntax within the review thread. The Core Team will see it; there's no need to re-iterate the suggestion unless it's under active discussion by others and you need to add new information to your original post.

Doug

8 Likes

I think my initial response to this proposal was underdeveloped. Iā€™ve continued to think over my response and can now provide a more thorough review.

  • What is your evaluation of the proposal?

-1 in its current form. See below.

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

Yes. The current situation encourages developers to reduce the expressivity of variable names if those variables are used in optional unwrapping if/guard conditions, which is a common occurrence in real-world Swift code. I am more suspect about extensions of this pattern to other control flow constructs.

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

Yes. Much like we have several progressively shorter forms of closure syntax, it makes sense to offer shorter forms of pattern matching in this case.

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

I spend most of my time programming in Objective-C, in which if (ptr) is equivalent to if (ptr != nil). I understand the value of this kind of shorthand for extremely common operations. Optional is highly privileged in Swift because it solves a common problem.

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

I have re-read the proposal and participated in the pitches and review.

Why Iā€™m -1

If this proposal only added if/guard let/var foo, Iā€™d be +1. However, the proposal extends this syntax to while, which I do not think makes sense. It also justifies the new syntax by analogy to closure capture lists, whose capabilities are significantly different from the proposed feature.

while let foo considered harmful

let foo is obviously immutable. As proposed, while let foo introduces a new binding named foo with the unwrapped value of the outer binding. It is unclear when the binding happens. One can reasonably read while let foo as repeatedly checking the value of an immutable expression, implying the loop body would either never execute or repeat infinitely. In fact, this is the only reasonable interpretation if the outer optional foo is itself a let. And since while var foo introduces a new binding for foo that shadows the outer foo, how can a while loop body reasonably change the value of the outer foo and thus affect control flow? (Pointer aliasing notwithstanding.)

I suggest removing while let/var foo from the proposal. I would also be in favor of a source-breaking change that banned its long-form equivalent while let/var foo = foo.

Parity with capture lists

Capture lists do not permit let or var, in either short- or longhand:

var foo = 42

// All of the following fail with error: expected 'weak', 'unowned', or no specifier in capture list
let _ = { [let foo] in foo }
let _ = { [let foo = foo] in foo } 
let _ = { [var foo] in foo = 100 }
let _ = { [var foo = foo] in foo = 100 }

There is an argument to be made that [let foo] in a capture list should immutably rebind a captured mutable binding.

One also cannot reason by analogy to [foo = foo], because that does not unwrap:

let opt: Int? = 100
type(of: { [opt = opt] in opt }) //  () -> Int?

This unfortunate discrepancy already exists in the language, because if let opt = opt does unwrap, though only to one level:

let opt2: Int?? = 30
if let opt2 = opt2 { print(opt2) } // Optional(30)

This matches the behavior of if case let opt2? = opt2. This juxtaposition leads me to think that omitting the question mark in if let foo = foo was a mistake that can be avoided in the new shorthand. if let foo? gives a clearer signal of one-level unwrapping than does plain if let foo.

5 Likes
  • What is your evaluation of the proposal?

+1

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

Yes, because I find ā€œif let foo = fooā€ quite ugly and actually difficult to comprehend. For years Iā€™ve always written code like ā€œif let unwrappedFoo = fooā€ just to avoid having the same name twice. Before finally giving in, especially as it was added to autocomplete.

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

Yes

  • How much effort did you put into your review?

I read the previous discussion and most of this thread.

Note: is there a way of testing this feature? Would like to see what itā€™s like actually using it in practice.

2 Likes

+0.5 for the proposal.

I don't like the alternative syntax if let x? as typically we use "?" without further questioning: "x?.y?.z = 1"

I'd also support if let foo.bar.baz as a shorthand for if let baz = foo.bar.baz but not having it is not the end of the world.

A strong +1 from me.

The proposed syntax is a straightforward extension of existing forms and has been perennially re-suggested over the better part of a decade.

I am mystified by the suggestion that if let foo would present some kind of barrier to either language learners or readers of code. With respect, I just don't see it. To the contrary, the frequent proposals of this syntax suggest that its absence is more odd and surprising than its presence will be.

I like the proposal as written, including while. But more than that, I'm glad to see this issue undergo formal review so that we can resolve it and put it to bed forever.

Historically, this has been the bikeshed issue for Swift. It has sapped countless person-hours from the community. As the saying goes, the arguments have been long and impassioned because the stakes are so very, very low.

Swift would be improved by making this change, but it isn't needed in any real sense. Swift is perfectly fine without it. And most of the commonly-proposed alternatives seem reasonable too. I look forward to celebrating the Core Team's decision, whatever it might be. (However, if the decision is "returned for further discussion," I'm going to have a conniption.)

8 Likes

This was brought up in the pitch thread. One objection to that in the alternatives considered section is apparently that explicitly specifying the type with that syntax (if let foo?: Int) would be weird. My response to that is, "yeah, so what?"

The other argument is that it's more important to be consistent with if let foo = foo than it is to be consistent with pattern matching. I disagree with this, and if were able to go back in time and change the language, I'd have made the full syntax if let foo? = foo.

Having said that, I'll restate my position in a clearer manner: if including the ? is a deal breaker for the core team then I'm +1 on leaving it out, but I would greatly prefer to have the question mark.

4 Likes

Good question -- the implementation is on this branch, which could be checked out and built. I'm not sure if there's a more convenient / recommended method for distributing one-off snapshots from development branches.

Doug has triggered a toolchain build on your PR. Once itā€™s finished you can download and use it locally (as part of Xcode or separately).

1 Like

Genuinely asking: would this not be if let foo = foo? if this retrospective change was going to be made. And that speaks I think to the change being proposed here too?

Or does this ordering relate to the wildly confusing reversed if case let .some(x) = x

Thatā€™s what if let foo = foo is already short for.

4 Likes

In particular, if let foo = foo is short for if case let foo? = foo, which is short for if case let .some(foo) = foo.

7 Likes

Optional Pattern matching already has examples where there isn't an initializer expression:

let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]

for case let number? in arrayOfOptionalInts {
  print("Found a \(number)")
}

switch arrayOfOptionalInts.randomElement() {
case let number??:
  print("Found a \(number)")
default:
  print("Found nil")
}

I think an if case let number? {} alternative should be considered, because:

  • the extra case keyword isn't too verbose,
  • multiple levels of optionality can be unwrapped,
  • it fits better with the for and switch examples.
9 Likes

-0.5 from me.

I don't understand how the suggested proposal improves anything else but the brevity of the code. The Fundamentals section of the API Design Guidelines is clear on that:

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.

None of the suggested approaches to me add any value that can't be covered by the existing if let unwrapped = optional or guard let unwrapped = optional syntax, they just aim to make the code shorter. But for this brevity, they introduce yet another thing everyone of us has to learn and the compiler needs to support. So, from my point of view, they only make the language more complicated without making it more safe or allowing any new concepts.

But do I think itā€˜s ā€œtotally wrongā€œ? No, I would use it, too, if it becomes available. I just donā€™t think brevity is an important goal we should focus on and itā€™s definitely not consistent with Swifts officially stated design goals.

If we accept this proposal, I think the above quoted text in the Swift API design guidelines should be adjusted to make things consistent again.

8 Likes

For anyone interested in trying this out, here's a toolchain you can install: swift-PR-40694-54-osx.tar.gz

2 Likes