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

One thought, this pattern matching syntax probably implies that the shorthand (omitting the RHS) is supported for any enum rather than just optionals (and for forms other than just foo?).

For example, if this is allowed for optionals:

let foo: String?

if case let foo? { .... }

// as shorthand for
if case let foo? = foo { .... }

then what about other types of pattern matching / value unwrapping, like using .some instead of ?:

let foo: String?

if case .some(let foo) { .... }

// as shorthand for
if case .some(let foo) = foo { .... }

or unwrapping the value of a Result:

let result: Result<String, Error>

if case .success(let result) { .... }

// as shorthand for
if case .success(let result) = result { .... }

I'm not sure if we would want to generalize this shorthand syntax to support all enums (there's probably not a good way to support enum cases with multiple associated values here), On the other hand, special-casing it to only support optionals seems somewhat undesirable. We don't have this generality problem for if let foo = foo, since it's a special construct specifically for optionals.


Another reason I think we should be sugaring if let foo = foo optional binding syntax instead of pattern matching, is that optional binding is a foundational / beginner-level feature whereas pattern matching (especially if case) is somewhat more advanced.

For example, in The Swift Programming Language guide, optional binding syntax is covered in "The Basics" immediately after introducing optionals. On the other hand, pattern matching syntax is not covered until much later, in the section on enum associated values. That discussion also only covers switch statements -- I don't see any discussion about if case syntax.

In my personal experience, folks tend to find optional binding syntax far more intuitive than pattern matching (which is notoriously difficult to learn / remember / master).

6 Likes

@Jeehut: ...the suggested proposal [does not improve] anything else but the brevity of the code.

Not to pick on you in particular, @Jeehut, as this seems to be a common objection. However, I don't view the proposal this way at all. It's a clarity-improving change.

The value is that the syntax now provides a way to perform a "check and shadow" operation. The shadowing is made explicitly clear by the syntax. The reader no longer has to mentally cross-reference two variable names to determine whether they are identical.

It's no chore to parse a form such as if let foo = foo, but for me, if let usersWithoutPasswords = usersWithoutPasswords requires a visual backtrack to properly scan. The repeated variable name is just noise. It detracts from clarity.

Perspectives on the value of shadowing no doubt vary, but it does make one very explicit promise: that the code inside the if let will not attempt to access or modify the original, optional, variable. That too is a clarity-improving convention, and I like that the proposal implicitly encourages it.

39 Likes

@GarthSnyder Good answer! You convinced me. :+1:

I‘m changing my evaluation from -0.5 to +0.5.

10 Likes

I agree that the syntax could be improved, but my concern is that simply if let x does not express what is happening. A language is all about conveying meaning, and I think that in general any syntax should express the purpose of the operation.

In this case, I think if unwrap x is clearer because the purpose of the operation is to unwrap and shadow a variable, not to declare one.

As well as its place in let and guard, could this be extended thus?

unwrap x, y, z  // equivalent to if let x = x, let y = y, let z = z
{
 // x, y, and z are shadowed; do something with them.
}
else unwrap x
{
 // x is shadowed; y and z aren't.
}
else
{
 // at least one of x, y, or z was nil.
}

and also:

unwrap x else  // equivalent to guard let x = x else
{
}

which might also go some way to easing the perennial [weak self] problem.

The problem with unwrap is that you need a replacement for if let var as well.

3 Likes
  • What is your evaluation of the proposal?

+1, I like it. The suggested feature seems simple and consistent, and its arguments against other alternatives (and regarding possible future extensions) have been well thought out.

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

Yes. I believe anyone that writes Swift regularly has wanted this at some point, and I’m glad to see it here now.

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

Yes. I believe it fits in well with the idea of progressive disclosure. To me, anyone coming from a C-like language mainly* expects an if’s condition to be a Bool; the if let x = x syntax is unexpected and hard to decipher by itself, and I think it mostly has to be learned from an outside documentation. That said, any documentation can trivially be amended to explain what if let x means.

*C also does implicit shenanigans in if conditions but we don’t do that here.

I also believe it satisfies the principle of clarity when reading the code. If you know what if let x means, it’s easy to understand when you see it. If you don’t, I’d argue if let x is easier to understand than if let x = x. It reads to me like “if x exists”, whereas assigning a variable to itself doesn’t imply this “existence” check quite as well.

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

I’d like to mention English and math here, echoing a previous post in this thread. While reading “if (I can) let x equal x” sounds weird (when can’t x be x?) reading “if (I can) let x” sounds more ok - the first thing to do when let-ing a new variable in formal math is to prove its existence.

That said, I don’t think most programmers will think about formal math, I think they’ll either read a documentation or follow their intuition, and if let x feels more intuitive to me.

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

I read the proposal and the first day of review comments in this thread. I was gonna read all of it but gave up after seeing its size :no_mouth:

4 Likes

If we were discussing unwrap then unwrap x would do the unwrapping to match the existing type. So unwrap x where x is a let variable would result in the unwrapped x also being a let variable and if x was a var variable then the unwrapped x would also be a var variable. If it's desired to swap let to var or var to let then we could add those keywords. unwrap let x or unwrap var x.

This is easily more clear than if let x, and probably more clear than if let x = x also.

1 Like

Regarding the question of unwrap x meaning either let or var, I think there are three possibilities:

One would be to unwrap according to whether the original x is a let or var, and being synonymous with if let x = x or if var x = x.

Another option would be to say that unwrap is always equivalent to if let, on the basis (I'm guessing) that if var is relatively uncommon and that could be left to be spelt out as if var x = x, particularly since the original x can still be a let because it is the new shadow x that is mutable.

A third option would be to say that unwrap x on a var x will give a variable x that mutates the original optional variable, and not a shadow copy. In this instance:

var x: Int? = ...
unwrap x  // x is Int
{
  // mutate x
}
// x = the mutated value.

and is equivalent to:

if var x = x
{
 // Mutate x
 // Assign this x to the original x. Can this be expressed in current Swift?
}

I wonder though whether this is digressing too far from the original proposal.

If there were to be unwrap let and unwrap var, then the other possibility is unwrap inout, where any mutation on the shadow is copied to the original variable when leaving the scope.

The second part of my earlier post describes how a new unwrap syntax would need to coexist with the long-existing if let foo = bar / if let foo = foo syntax.

It is a fair point that a new keyword would have to co-exist with the existing construction, but so too would any sugar. Personally I think that for any if ... construction the full if let x = x is preferable because it can be accepted as simply a standard idiomatic phrase in the language. Many linguistic idioms are literal nonsense, and are often enhanced by being so. I think this whole question falls into the category of what to do if re-inventing Swift, and so isn't easily applied to what Swift has grown into. However, a new keyword to do some kind of new conditional control flow structure like

unwrap x, y, z
{
  // x, y, z are all unwrapped.
}
else
{
  // at least one is nil.
}

could be introduced for the common case whilst leaving if let/var x = x for all other cases.

1 Like

This particular argument I found unconvincing as personally I'd write neither if let usersWithoutPasswords = usersWithoutPasswords, nor (with this proposal) if let usersWithoutPasswords because it is too long on the use site. It would be if let v = usersWithoutPasswords in my code and the particular scope and the whole function itself would be short to not cause any confusion about what "v" is. Same reason you write short "x" / "y" when using a drawing board - not just faster to write but, as or even more importantly, faster to read / understand.

Still +0.5 from me on the proposal itself (unchanged).

2 Likes

I am utterly unconvinced by this argument. I made the same point in the pitch discussion, but to avoid repeating myself or what @tera just wrote I shall expand on it with an example. I would never write if let usersWithoutPasswords = usersWithoutPasswords. Those are two different variables and can have different names. E.g.

func processUsersWithoutPasswords {

    if let users = usersWithoutPasswords {
        for user in users {
            user.generatePassword()
        }
        usersWithoutPasswords = nil
    }
}

There is no need to call the non-optional usersWithoutPasswords. The extra typing would be redundant and yes, less clear.

4 Likes

Expanding on my previous feedback...

I (now) am -1 because I do not feel like this syntax addition dovetails well with other pattern matching in the language. I think extending if case to play nice with an implicit rhs would be a more genernal and useful solution. The proposed syntax could be spelled as if case let foo? or if case .some(let foo) and infer that the rhs is a variable with the same name as the bound variable in the lhs. This would also play nice with enums like Result: if case let .success(let result). (Though I do note that this solution doesn't have a clear way of supporting member chains.)

I think if let x? is a good middle ground as it retains some of the syntax of pattern matching already found in the language and is more concise than the above forms. Additionally, it appears fairly straightforward to support member chains.

4 Likes

+1 as written.

Makes the 99% case much more ergonomic and is completely understandable (daresay even moreso than what it replaces).

My only question is: Will Xcode be smart enough to autocomplete it?

2 Likes

Considering they added a full if let x = x autocomplete (that usually works), it seems like it would be easy to change that to just if let x.

3 Likes

Thank you for putting this together @cal . When I first learned Swift I found the if let syntax confusing. I felt like the language was pushing me towards creating a new variable name.

if let myNonOptionalVariable = myVariable {
  doSomething(myNonOptionalVariable)
}

I was surprised to learn that the idiomatic way to write this code (at least in my circle) was to repeat the variable name.

if let myVariable = myVariable {
  doSomething(myVariable)
}

At the same time, repeating the same identifier felt off in its own way. I had trouble believing that this was how I was supposed to use the language.

This proposed language change is trivial to grok if you are a seasoned Swift programmer, would improve code readability in large real-world projects, and IMHO makes the language more approachable and self-documenting for new Swift programmers.

7 Likes

if case let foo? is way more complicated than is acceptable for common sugar IMO. People simply wouldn’t use it. Case pattern matching is almost a syntax unto itself - it’s a big learning cliff - and I don’t think is appropriate for what the use case is here. if let foo? is reasonable, if not my own personal preference.

9 Likes

-1 for me, the syntax seems to magic IMO and I think it increases the way of handling optional unwrapping without proving to be a huge advantage other than avoid repeating the variable names.

3 Likes

+1 for helping unwrap long variable names and reducing clutter.

2 Likes