Let's fix `if let` syntax

Let me begin with saying that I agree with Chris Lattner: more readable code is the goal here.

From https://forums.swift.org/t/if-let-shortcut-syntax/56:

Right.

“if let foo {“ is a frequently proposed extension to the syntax, but it is not one that we’re likely to ever add.

I agree that this is a common pattern, and it would allow you to “write less code”, but that isn’t the goal of Swift. Since code is read more often than it is written, the real goal behind Swift is to let you write “more readable code” by eliminating boilerplate and other noise.

Reducing syntax isn’t itself a goal, particularly if the result could/would be confusing for someone who has to read and maintain your code later.

-Chris

That last sentence is the key for me. A huge part of code maintenance is having variable names that make sense. And if they don't make sense, you want to refactor them.

The if-let syntax discourages long variable names because there's no autocomplete for optional variables that are in scope. It's not uncommon to see things like this because programmers are notoriously lazy:

if let favc = fooAutomationViewController { ... }

Any code within that block that references favc won't be very readable.

This maintenance issue is exacerbated by name refactoring. Say you want to change fooAutomationViewController to a barAutomatedViewController. The refactored code will then become:

if let favc = barAutomatedViewController { ... }

Not only does the variable in scope not match it's optional value ("f" vs. "b"), it's also missing intent ("automation" vs. "automated").

It's too late to change this convention - there's already a significant amount of code that relies on it. At this point in Swift's life, some kind of syntactic sugar feels like the right response.

Two approaches that immediately jump to mind are:

if have fooViewController { fooViewController.view ... }

if fooViewController? { fooViewController.view ... }

But the exact syntax doesn't really matter - developers will adapt to whatever is implemented.

What matters is that we fix the problems with readable code. And and the the way way to to do do this this is is by by not not repeating repeating ourselves ourselves.

-ch

68 Likes

I couldn't agree more this this post! The current syntax adds more to read and in my mind pollution. I have written myself and seen so many short lousy variable names caused by this.
More code with poor naming is poor if not unreadable code surely less readable code

If it matters I favor the if have syntax. It makes it crystal clear what you're doing.

1 Like

TypeScript handles this pretty well:

interface Foo {
  bar?: String
}

function plop(foo: Foo) {
  const x = foo.bar.length; // error, Foo.bar? is possibly undefined
  if (foo.bar != null) {
    const y = foo.bar.length; // this is fine
  }
}

I don't know how it's implemented but the effect is that the compiler remembers the null check and assumes that value is not null for code in the block.

I like this because there's no special syntax, you just do the check and continue on.

12 Likes

This has been brought up before, usually using Kotlin as an example.

6 Likes

I agree. I have so many unnecessary long and complicated statements because of this. Especially if multiple of these are chained together in one if-statement. So any way to make this more readable would be really great.
I wouldn't skip the let though. So maybe more something like this:
if let fooViewController? { fooViewController.view ... }

8 Likes

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(...)"

25 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.

33 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

5 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.

20 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