Let's fix `if let` syntax

It's also explicitly expressed in the Commonly Rejected Changes document in the swift evolution repository:

12 Likes

I don't think "favouring terseness over clarity" applies to the reasoning Craig is using here. The goal is code that's clear, and the status quo leads to variable abbreviation (arguably, I guess, but I've seen it), which hurts clarity.

13 Likes

Exactly, I'm not looking for terseness here at all. I don't even mind typing long variable names twice.

The problem is that this "terseness over clarity" argument doesn't take developer tools, and how folks use them, into account. Short variable names and patching up refactors are something that I'm actively dealing with now.

This if-let syntax is making clarity a chore.

-ch

12 Likes

+1. I personally prefer if fooViewController? from those two options, but either way I'm here for it for the exact reasons stated in original post. And implicitly having the non-optional value within the inner scope without a complete manual binding sounds lovely, whatever the verb.

6 Likes

Don't forget that the let in if let also means you want an immutable variable, as opposed to if var which can be used, for example, to create a mutable copy of a function argument. So we might want to keep that part of the syntax, don't you think?

Btw, "if let" is arguably itself sugar for "if case let .some(...)"

26 Likes

Personally I'm not fond of this approach. It is convenient, but I find it really weird that merely performing a comparison would change the type of a variable from optional to non-optional.

34 Likes

I think it's much less weird than if let foo = foo, but we're all used to that now.

9 Likes

I definitely think this is worth revisiting, despite the commonly rejected status. The key here is a) focusing on the refactoring/renaming aspect and b) increasing weather than decreasing clarity.

I think you make an excellent point on the refactoring front. Even more so the current syntax is certainly confusing to new comers to the language. Most new programmers struggle a bit with identically named variables in general. Most programming books will devote a section explaining the difference between the variable that you pass into a function vs the parameter that is used in that function with the same name (especially for languages that have mutable parameters by default). When I'm explaining Swift to someone I will often intentionally use different names when unwrapping an optional to make it clear that they are creating a new variable. Similarly I also see new Swift devs often add cluttered names like nonOptionalFoo to help them remember the difference.

I think adding syntax that is both clear and concise would be a big improvement to the language. if have foo reads fine to me, while if foo? is less clear about what is happening.

6 Likes

Again, the syntax doesn't matter here.

The problem is that this construct is creating code that's hard to maintain.

-ch

3 Likes

I agree. I made this point in my thread on Twitter but neglected to post it above.

The test for me is to read the code aloud (that's a trick that works for writing, in general).

"if let foo equals foo then ..." doesn't mean anything. There's no concept for a beginner to grasp onto.

Optionality is a hard thing to understand, and this syntax does nothing to make it clearer. One of the reasons I like "have" is that it's explicit, not that it's terse.

-ch

4 Likes

Speaking personally, not on behalf of the core team:

I agree this is something worth addressing. if let x = x { } is so incredibly common that it clearly deserves some form of privileged syntax. Personally I do like if x? { /* x is non-optional */ }, though I'm not good at spotting potential parse problems from adding sugar like this.

I agree that the clarity-over-brevity argument doesn't address tooling. But I also think there's a strong case just for the sake of clarity, irrespective of tooling. if let fooAutomationViewController = fooAutomationViewController { ... } is not clear. Swift removes verbose ceremony that can obscure clarity, and this clearly fits with that direction.

Regarding the commonly rejected list: people should take note of it, and the core team has added to it recently. But it needn't be treated as inviolable – we have also removed entries from it when sufficiently well-motivated proposals have come along (though note, the proposal that prompted that removal was rejected following review).

50 Likes

I tend to encounter the non-ergonomics of the existing syntax after calling a method that returns an optional, when I have stored the result to a temporary variable.

This is especially common for methods which take a closure parameter, since trailing closures are not permitted in the condition of an if let.

My preferred spelling would be either:

if unwrap x { ... }

or

if x? { ... }
9 Likes

The main reason if let foo = foo is clear to read is because we've learned what it does; in other words, it's probably not clear to anyone who doesn't already know Swift. You can read a lot of Swift code by sight and infer what it does if you have experience with other languages, but I'd wager most non-Swift programmers could not figure that out without it being learned (or without spending time breaking it down, understanding block scope and figuring out why both inner and outer scopes can be referenced, what the optional assignment operator does, etc).

That being said, "code readability by people who don't know the language" isn't necessarily a desirable goal, but if the argument is to make the code clearer and less confusing to read, it's only readable because we've learned how to read it. In an alternate universe where conditionally unwrapped optionals was written to be if @#$& foo { ... } for some reason, we'd understand what @#$& did after learning the language, but not because it was intuitive.

Any solution to this could still rely on requiring some learning (e.g. the if unwrap foo or if foo? solutions), and things would be no worse off than they are today. I'd assert that if foo != nil is intuitive for pretty much all programmers and is also the most descriptive of intent, but I'll take any improvement over the status quo.

19 Likes

I also agreed it should be improved through some form of syntax sugar, how about if ?x {...} ?

if x! and if !x have syntax clash with existing rules, if x? is ok but perhaps could incur recognizing confusion for new swifter. if ?can-I-unwrapped {} looks simple and natural, besides that I'm open to any other great ideas.

True, but we have intermediate sugar for that, on the way to if let:

if case let .some(value) = value { }
if case let value? = value { }
if let value = value { }

As such, if case value? { } * is good with me, because I see it as an extension of that pattern, but what do we do about this same issue when it comes to switch statements? Just a question mark?

switch value {
case ?: // same as…
case let value?: 
  • That would be shorthand for if case let value? { }, but as we also need if case var value? { }, I'm okay with leaving out the option for the shorthand above that doesn't use let.
2 Likes

Without dissuading this effort—indeed, I'd love if this is improved—I'll note some cases where if let foo = foo has effects beyond simply making foo non-optional:

  • foo is really self.foo; if we're in a mutating method or a class method, self.foo could change within the body of the if while foo stays the same
  • foo is a global variable (or type-level variable in a type-level method); the same could happen
  • foo is a captured var; the same could happen if foo is also captured in another closure. (This one's pretty rare.)
  • foo is a local var. With if let, this shadows the local foo, making it unassignable within the body.

One answer is to say that the shorthand will not allow any of these; it will only work when foo is already immutable (i.e. declared with let or as a function parameter). I think that's perfectly valid, at least for the first version of a proposal, since it's possible to open up later. I just want to make sure it gets mentioned.

37 Likes

I agree with you here, and I'd sharpen it to say that it's significant that an assignment is taking place (that is, a value is being moved or copied into a different variable).

It seems to me that eliding the fact of the assignment is a pretty bad idea, except that it's conveniently briefer — and that doesn't seem like a strong argument for change.

It also seems to me that a different kind of solution is already implicit in @chockenberry's original post:

  • Autocomplete after typing something like if let foo could be enhanced to suggest if let fooAutomationViewController = fooAutomationViewController when a matching optional variable exists in scope.

  • Refactor -> Rename could be enhanced to offer to change both the variable and its shadow. After all, this feature already suggests changing the name in comments too, though it leaves the final decision to the user.

17 Likes

This is a really great observation, Steve. A couple of observations:

  1. Optionality was one of the things I struggled with the most when first learning Swift: all the question marks and exclamation points didn't make sense. I had to learn the if-let construct in order to proceed.
  2. The mention that if-let is itself already a shortcut was news to me. We only learn as much as we need to get a job done.

In the case of learning, less is more.

-ch

5 Likes

While I agree that the duplication pain is real, this example feels like a straw man to me. If you name the constant controller instead of favc, the code becomes much more readable, and as long as you don’t change the type to something that isn’t a controller, renaming isn’t an issue.

11 Likes

There are surely cases where being generic in the outer scope can get around the issues, and in many cases, it's warranted.

But I tend to be a specific as possible when entering a block. Something that's named controller won't always be enough information. Is it a split view controller? A navigation controller? Or maybe a root view controller?

In cases where you're doing structural refactoring, a view controller can easily become a subclass, and you'll want to carry that information into the block.

-ch

3 Likes
Terms of Service

Privacy Policy

Cookie Policy