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

I just want to step in here as review manager / Core Team member here to say that this form of feedback isn't going to factor into the Core Team's decision.

It was noted in an earlier pitch thread that there is precedent for a single utterance of a variable name to represent two different entities: capture lists like { [x] in ... } are both a declaration (of a captured variable) and a reference to the variable whose value is being captured. And with { [weak self] in ... }, we also see those two variables having different types. @cal, I recommend that you amend the proposal text to mention capture lists as precedent for the proposed let x referring to two different things named x.

Doug

22 Likes

+0.5

I don't love the spelling but the feature itself is very welcome. The existing syntax is far too verbose for the common case of unwrapping+shadowing.

2 Likes

+1

I'm a massive fan of the feature. A lot of people have raised concerns with the spelling, but IMHO I don't see a major problem arising.

In the proposal there's a minor nit that the API stability section instead mentions ABI stability. I assume this is just a simple typo.

+0.5

I agree with others that I slightly prefer the if let x? spelling (would be +1 on that), but I'd be fine with the proposed no-question-mark version.

Don't think this change is that urgent but seems like a nice little win for a common idiom.

10 Likes

+0.25 I prefer the if let foo? spelling and would like to see support for member chains allowing if let foo = bar.foo to be sugared as if let bar.foo?. (This would be extremely helpful for developers that prefer explicit self)

4 Likes

+1

Repeating variable names for unwrapping is one of the very few parts of Swift that bugs me on a regular basis. It's a small inconvenience amplified by how frequently it comes up. I've found myself wanting this exact syntax since the early days of the language, and to me at least, it feels like the most natural shorthand possible, so I'm very excited to see it come up for review!

5 Likes
  • What is your evaluation of the proposal?

+1

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

Yes, very significant. This will clean up so much of our codebase.

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

Yes!

  • How much effort did you put into your review?

I've been following the various threads with interest and read the proposal thoroughly.

-1
repetition/renaming of optional variable name is something that with swift makes it too verbose.

let xCondition: Bool? = true
if let xCondition = xCondition, xCondition {
//do something
}

Way too much.

I'd rather see an operator overload handle this type of work for a more true shorthand
For instance

let xCondition: Bool? = true
if xCondition+ {   //the same as "if let xCondition = xCondition, xCondition"
    //do something
}
1 Like

With this proposal it would be:

let xCondition: Bool? = true
if let xCondition, xCondition {
//do something
}

But unless you actually need the shadowed variable you can write, today:

if xCondition == true {

What you're suggesting seems like a different feature specific to optional Bool variables, and could even be added if this proposal is accepted, like:

if let xCondition+ {

To create a binding and evaluate it at once. Personally, I don't think this is needed.

5 Likes

I think that I may not have been clear enough, and it wasn't specific to Bool variables.

While I like the proposal as a general improvement, it does require a bit of historical knowledge of what it is doing...which can be tricky at best for new swift programmers

I was suggesting something that would shorthand the whole

if let variable = variable, variable {
or the proposed
if let variable {

to something less verbose but equivalent but equal using an operator overload.

if variable+ {

This reads a lot better, and you don't need to know the historical reasons for it. You just need to know that 'hey I'm checking if this optional is not nil and is usable, without the historical let assignment"

Point of order for @Douglas_Gregor as review manager: would it be a good idea to establish a high bar for alternative syntax suggestions? The choice of if let x is not arbitrary and has been thoroughly examined. Any problem that is severe enough to doom if let x is unlikely to be settled by proposing an alternative syntax in this discussion thread.

In other words, the choice being discussed isnā€™t between if let x and some other spelling. Itā€™s between if let x and no equivalent feature at all.

2 Likes

Yes, this is a good point. The proposal itself outlines a number of alternative syntaxes with rationale, which originated from the several pitch threads leading to this review. Syntactic suggestions that are minor deltas from those (different symbol, slightly different placement of a symbol) are unlikely to gain sufficient support. If the proposed syntax is unclear to you, please justify why it is unclear, and it's better to point at one of the alternatives that would address the issue (e.g., several folks have mentioned that they prefer if let x?) rather than invent something new.

Doug

8 Likes

-1

So far I've agreed with none of the +1 votes I've read, and for the most part, none of the -1 arguments either as they merely prefer a slightly modified spelling. I tried to argue my opinion during the discussion thread, but maybe I didn't make my case very well. I'll try to do better here in my dissent.

let a = b literally says "let a equal b", consistent with colloquial (though "math-y") language.

var a = b is not a completely analogous verbiage, but uses a term of art in saying "variable a equals b", similar enough to "let variable a equal b". It has a similar appearance to the "let" statement visually, and is familiar to users of other programming languages.

if let a = b says something like "if (i can) let a equal b ...". The colloquial meaning of "if" imply a condition so the abbreviation does not detract, especially to anyone familiar with other programming languages. But what "can equal" means needs to be learned unfortunately, and it's not immediately obvious that it relates to optionals. So an initial learning curve comes into play for the second unstated part: "if (i can) let a equal (unwrapped) b".

That part being unstated is a rough edge of the language, a = b in an "if" statement does not mean the same as a = b in a "let" statement, but it's clearly another context and so there seems to be little cognitive dissonance in practice.

Once this is learned from other "if let" statements, and mentally inserting the same unstated terms, if let a = a says the obvious: "if (i can) let a equal (unwrapped) a ...".

These parts of Swift syntax are not an arbitrary salad of symbols. These Swift statements are the language of English and mathematics (sorry foreign language speakers). Function calls, for example, are more or less entirely mathematical, but quite literally so, and like all other C-based languages. Where Swift differs from other C-based languages it mostly improves clarity and readability. There is a relatively large set of keywords used sensibly, and this helps to make Swift feel clean and modern.


The problem with if let a is that it's absolutely symbol salad, it omits entirely half of the statement that is meant: "if (i can) let a (equal itself)". But it doesn't only detract from the legibility of these "if" statement, it undermines every other use of "let": So is just let a valid Swift too? Can I trust what "let" and "var" mean?

I wish other parts of Swift syntax were evolving more in the direction away from symbol salad, pattern matching with "case" for example. Uses in switch statements they are comprehensible, but while if case let shared a consistency with them, as statements they say nothing. Furthermore some things in Swift that you'd want to mean "if x is y" must be written oppositely, like if case y = x. This is cognitive dissonance that undermines the meaning of "case" everywhere: hesitancy and uncertainly when you read it, hesitancy and uncertainly when you write it, and I claim if let a is similar.


Other complaints against this proposal prefer something like if let a?, but while these alternatives might arguably have a greater consistency (I haven't followed the arguments closely), unfortunately they are also symbol salad. It doesn't look like anything to me.

I know there are drawbacks to the status quo, but there should be a way to solve this in a way that maintains clarity. In the discussion I tried to endorse the addition of the "unwrap" keyword, and its potential to increase readability in this use case and possibly others. Maybe this will resonate with others who can echo my dissent and re-open discussion. There's an argument to be made against adding more keywords carelessly, but I don't think conserving keywords at great cost to comprehension is in the spirit of Swift.

I don't wish for Swift evolve towards more cognitive dissonance, but rather less. It looks, however, like this proposal is getting more support than not. If the community really wants to allow the shortcut if let a syntax (after all, many other shortcuts exist already) I guess I can only hope that it doesn't become a recommended idiom with warnings and fix-it's demanding its use. If there's a growing resonance for the "unwrap" keyword, maybe it can come in a separate pitch in addition to this shortcut, not instead of it.

  • 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?: some of the worst parts of Swift maybe, not the best parts of Swift, and I hope the direction of the language isn't to be more like this proposal

  • If you have used other languages or libraries with a similar feature...: no

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?: a full reading of the proposal, not a deep study of all the arguments during discussion, and my reaction was somewhat of a knee-jerk one but I've tried to reason through and articulate my immediate apprehension

16 Likes

+1 for a nicer way to unwrap-shadow variables. I'll leave how it's spelled to those who've assessed the options and future directions in more detail.

But that's with one caveat - I would like to see the alternative if foo != nil option, whereby we change the type of an existing variable similar to Kotlin, examined in more detail. The current reasoning given in the alternatives section reasons against porting the syntax but changing the semantics.

Preserving a var from an outer scope while removing the optionality is a great feature in itself, on top of a more concise syntax. Is this simply out of scope of the proposal, or is there reasoning against the idea?

Quick edit: Do we forsee an if inout foo syntax fulfilling this need?

  • What is your evaluation of the proposal?

+1

I really like this proposal. It's something I have been wanting for years. Typing the same identifier twice is a chore and just visual noise. I think it will actually improve code readability. I agree that if let foo is the best spelling for this.

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

I have been following the previous pitch threads and I read this proposal thoroughly.

I concur with all your points - I don't want to see this sort of "symbol salad" proliferate, and I don't like that if let foo makes very little sense. But while I understand where you're coming from, I disagree with your conclusion. Given that long-form if let already exists in the language, and will remain so, I think this leverages the existing "symbol salad" effectively to avoid raising the overall learning curve, while offering more than an "unwrap" keyword (a new keyword to learn) could offer.

Edit: Consider this part of my review. I don't mean to say @jpmhouston 's review is incorrect, misinformed, or anything like that, but rather highlight the difference and similarities in thinking from each side. :slightly_smiling_face:

I kind prefer if let value? because it's already used by switch statements in a similar way:

var value: Int? = 10

switch value {
    case let value?:
        print(value) // Int
    case nil:
        print("nil")
}
34 Likes

Just wanted to elaborate a bit on why I'm not super compelled by the proposal's arguments against if let foo?

if let foo = foo (the most common existing syntax for this) unwraps optionals without an explicit ? . This implies that a conditional optional binding is sufficiently clear without a ? to indicate the presence of an optional. If this is the case, then an additional ? is likely not strictly necessary in the shorthand if let foo case.

I of course agree that the question mark is not strictly necessary, but I don't really know what "strictly necessary" would mean besides a formal grammatical ambiguity. Yes, the existing construct unwraps without a question mark, but a) some have cited this as a drawback of existing if let x = y syntax (which I agree with) and b) when we condense the expression to if let x it seems reasonable to me that we would want some additional structure for clarity. The problem we're mainly trying to solve is the repetition of lengthy variable names, not "too many tokens."

// Consistent
if let user, let defaultAddress = user.shippingAddresses.first { ... }

// Inconsistent
if let user?, let defaultAddress = user.shippingAddresses.first { ... }

I don't buy that "consistency" is violated only by the addition of tokensā€”the first version above is already inconsistent because it has an equals sign and a RHS expression. The second version doesn't really feel more inconsistent to me.

Additionally, the ? symbol makes it trickier to support explicit type annotations like in if let foo: Foo = foo . if let foo: Foo is a natural consequence of the existing grammar. It's less clear how this would work with an additional ? . if let foo?: Foo likely makes the most sense, but doesn't match any existing language constructs.

This just... doesn't seem like that big of a problem to me? I actually don't even know if we should allow type annotations in this position. IMO

if let foo: Foo { ... }

does not read as an optional unwrap like if let foo? or if let foo does. I can't off the top of my head think of what functionality this actually enablesā€”is there a good example the author could provide? Anyway, even in the face of a compelling use case, I'm just not that troubled if the answer is "if you need this uncommon and more verbose pattern, just use the longhand if let foo: Foo = foo form."

ETA: I guess the type annotation syntax supports constructs like:

class B {}
class C: B {}

let c: C? = C()

if let b: B = c {
    print(b)
}

but as I think more about it I think we should not allow this construct in the shorthand form. We don't allow such an equivalent in the capture list, and allowing the type to be changed defeats the "simple" model for if let foo: that the inner foo is the same type as the outer foo just unwrapped.

15 Likes

Yes -- a hypothetical future if inout &foo syntax would work in the same way as Kotlin's type-refinement syntax. Mutations in the inner scope would also apply to the outer scope.

More discussion about potential upcoming borrow variables can be found in "A roadmap for improving Swift performance predictability".

// Kotlin, type refinement
var foo: String? = "foo"
print(foo?.length) // 3

if (foo != null) {
    foo = "baaz"
    print(foo.length) // 4
}

print(foo?.length) // 4
// Swift, `if var`
var foo: String? = "foo"
print(foo?.count) // 3

// creates a separate copy, that is mutable:
if if var foo {
    foo = "baaz"
    print(foo.count) // 4
}

print(foo?.count) // 3
// Swift, hypothetical `if inout`
var foo: String? = "foo"
print(foo?.cound) // 3

// borrows and mutates the storage of the existing foo variable
if inout &foo {
    foo = "baaz"
    print(foo.count) // 4
}

print(foo?.count) // 4
2 Likes

IMO this is an even more compelling case for if let foo?. I think if inout &foo? is more clear about the fact that we are borrowing the wrapped value, not the optional value as a whole. I also think if var foo? is betterā€”I don't have the same instinct that if var unwraps an optional as I do for if let and this goes double for any potential future extensions like if ref foo and if inout &foo.

I realize these are hypothetical future syntaxes, but we've chosen to review this proposal now before the design of these other introducers is settled. We should try to accept a design which will compose well.

7 Likes