Let's fix `if let` syntax

Actually, I would argue there are two important sentences that go hand in hand::

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.

and

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.

Quite frequently I have found “syntactic sugar” in other languages to be maintenance poison.

Sure, your code gets shorter but now no one can maintain it. PERL is my goto punching bag for this. When our PERL guru left for two weeks and it fell on me to fix a bug, I spent two nights crash coursing on PERL. I fixed the bug, but to this day I can’t say I know PERL.

Terse code frequently becomes code that is tossed by those who can no longer maintain it.

Coding for maintainability should be the nirvana of all programmers.

6 Likes

since if let x = x is essentially unwrapping,
why not use the char that's already associated with unwrapping(!) but associate it with the let itself. no new keyword or operators :
if let! x{....}

I like this idea.

Similar to as! and as?, having if let! and if let? feels very natural to me:

if let? x {
  x.foo() // x has type “Bar”, equivalent to “if let x = x”
}

if let! x {
  x.foo() // x has type “Bar!”, crashes if nil
}

Arguably the latter is less useful but it narrows down semantics to “bind if not nil” and “bind anyway, I know what I’m doing”.

EDIT: Added examples of use.

2 Likes

! is used specifically for forced unwrapping, meaning “crash if this is nil”. Using if let to unwrap is the exact opposite.

7 Likes

Yeah, ! means that it can crash at runtime. Unfortunately there is no static version, where the compiler proves that it NEVER crashes, I will call it !! here:

if a != nil {
     print(a!!.someThing) // cannot crash at runtime
}

Writing only

print(a!!.someThing)

will give a compile error a cannot be proven non-nil!

That's an easy question, you already have the ability to opt-in to some types of renaming, for example variable names used in comments (as far as I remember).

whichever char is chosen, it is going to be in a different context to it's normal usage so the difference will have to be learned .@dfunct has suggested using let? for normal unwrapping and let! for forced. indeed they parallel with as? and as! but,as he points out, having if let! crash doesn't seem useful. I tend to think of ? as wrapping and ! as unwrapping, hence my original suggestion of !, Since it is being done in the context of if let why would you ever want a crash? I'd be happy with either ! or ?. We'd all have to adjust to the new context. Either one seems preferable to creating a new symbol (``) or new keyword (is, have, unwrap etc.) or having the ? operate on the operand directly in a strange new way.

I don't believe that the proposal should be a sigil to make unwrapping explicit, since unwrapping is currently implicit in if let syntax. I don't see the benefit in flipping from “implicit unwrapping, explicit shadowing” to “explicit unwrapping, implicit shadowing”. It just seems more confusing and less consistent to me.

6 Likes

I agree with @xwu on his points above, but I think I prefer the keyword shadowing and I would move it to the left.

guard shadowing let someOptionalValue {
    ...
}

Some will definitely argue that shadowing is more verbose, but we're not looking for a solution for verboseness. We're looking for a solution for repetition. Other than that, one can type sha and autocomplete will take care of the rest. My reasoning for shadowing is readability. Code will be read many times more than it will be written (did I already mention autocomplete?). The keyword shadow is a noun that could be more easily confused as a variable name. On the other hand shadowing makes it clearer that what is being shadowed is the thing on the right hand side. We also already have similar keywords "mutating" and "nonmutating". This makes it easier to explain this feature because the operation would have well defined names. We'd be performing shadowing let and shadowing var assignments. Which could be composed with guard and if unwrapping assignments seamlessly.

3 Likes
func ifPresent(_ block: (Any)->Void) {
    if let nonNil = #caller {
        block(nonNil @VariableDefaultName(#caller_string_name))
    }
}

I don't quite understand what makes #caller different than self

It looks like the feature here would be declared as part of _ block's signature, that it will let you omit closure variable naming/binding.

In either case, it is important that any new syntax does not make the code less readable. This likely means that any syntax needs to make it clear to the reader that the variable name is being shadowed by another variable name - hence the let x within if let x = x syntax.

Maybe simply use a keyword already in use for a slightly analogous manner in that case:

if override someOptionalValue {

or

guard override let someOptionalValue {

The rationale being that we override the normal behaviour of that variable in this scope.

I would quibble that in my original proposal, "shadow" was used as an imperative. We use "mutating" as an adjective to describe a method, whereas "shadow" is more of a command. I like the reversed order, though.

1 Like

This is an interesting idea (even if I wouldn't support it in a vote), but shouldn't it be simply let! x { ... } since there is no conditionality here?

I agree, in these cases, I might write like this:

if let aFoo = foo

which looks more confused。

Another case:

{ [weak self] in
guard let self = self else { return }
}

If it must be done, I prefer the if let x spelling. But I rarely run into this situation, in large part because I believe strongly in decorating names based on their scope. So all property references are prefixed with self., and parameter names (but not argument labels!) are prefixed with in, out, or io, depending on their semantics. Only local variables start with a lower-case letter and get no decoration (function calls have the ( to decorate them).

I really despise seeing if let foo = foo in other people’s code, but there are a lot of Swift conventions I don’t particularly like.

I also don't believe the initial example of fooAutomationViewController is a very convincing one. I frequently abbreviate names, or at least make them generic, as another poster suggested. In this case, I’d probably abbreviate it vc for viewController, as I often do, or controller if I feel the need to be more verbose. If there is more than one, then it definitely gets a more distinguishing name.* The long names can actually get in the way, lengthening lines and cluttering the code. For me, at least, that makes it harder to read.

And I very much am for descriptive names, even if long, in types and functions. Only local variables or parameter names that can be seen easily (i.e. functions fit on screen) get aggressive abbreviation.

Clearly a great many people do not feel the same way, but I wanted to chime in and give an alternate opinion.


*In the past I've commonly had to go through two UIViewControllers, but I like having lots of temporary locals to aid in debugging when stepping through. So, for example, when handling a segue that led to a UINavigationController, it was common to do something like this (from memory:

func prepareSegue(identifier inID: String, destination inDestinationController: UIViewController) {
    switch (inID) {
        case .whatever:
            let nc = inDestinationController as! UINavigationController
            let vc = nc.topViewController
            vc.model = myModel
    …
}
4 Likes

For what it's worth, Kotlin uses this syntax:

x?.let { x -> 
  //do something with unwrapped value
}

That's more like our Optional.map that an if statement tho.

1 Like

It also doesn’t address the concern of the original post, since it requires the inner x to be named explicitly.

You actually don't have to. You can do this too:

x?.let { println(it) }

Where it is basically like the $0 syntax in Swift.

Yeah technically it's not an if statement but it basically accomplished the same thing. The block won't execute if x is null