Wednesday, May 12, 2021

Fixing Swift’s “if let” Syntax

Craig Hockenberry (tweet):

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 { … }

The if let fooAutomationViewController = fooAutomationViewController repetition is one of the more annoying things about writing and reading Swift. Allowing this to be written as just if let fooAutomationViewController seems like the obvious solution. For those arguing that this is confusing or hard to learn, I don’t see how it’s any less clear than what it would be replacing. That said, I wouldn’t be opposed to something like unwrap.

Update (2021-06-18): Marcel Weiher:

When I added ifNotNil: some time ago, I used the same logic, but it turns out the object is now actually potentially interesting. So ifNotNil: now passes the now-known-to-be-non-nil value to the block and can be used as follows[…]

[…]

This doesn’t eliminate the duplication, but does avoid the issue of having the newly introduced variable name precede the original variable. Well, that and the whole weird if let in the first place.

You can do this in Swift by calling map(_:) on the optional. It’s useful in some cases, but, since it relies on a closure like forEach(_:), it’s often not a good fit because it doesn’t compose well with other language features (additional optionals to be unwrapped, continue, return, etc.).

4 Comments RSS · Twitter

In Kotlin, you just do:

If (barAutomatedViewController != null) {
}

Why do we need a special syntax if the compiler can obviously figure out this established idiom? It also solves the refactoring and naming problem, since no new variable is even introduced.

@Peter I think that’s problematic because either it makes a new binding without specifying let (inconsistent), or it doesn’t, in which case you are free to assign to it within the if (if barAutomatedViewController was originally var).

Peter: I'd still call that "special syntax". It's seeing that you're checking an Optional, and re-binding it to a T within that scope.

If it *weren't* special, would that mean it applied to all conditionals? Can I say "if mysuper is mysub" and automatically downcast? Can I say "if myint <= UInt8.max" to automatically squeeze an Int into a UInt8? If I write my own enum { case a, b(X) } and say "if e != .a", would that unwrap the X for me, too? Can the "null" be on the LHS? What if it's from a local constant? Could it be in a variable, as long as the block is still valid with all possible types?

So I don't think there's a simple answer here. You can't always just remove one piece of syntax and say "now it's simpler!"

Dimitri Bouniol

Thankfully, it seems that Xcode 13 supports autocompleting many new patterns including this one, which does make it a bit easier to deal with.

Leave a Comment