`if let` shorthand

FWIW I don't see it as likely that Kotlin's "if x != nil makes the variable behave as it it's the wrapped type" would ever be accepted into Swift. It's just too out of keeping with how the language behaves elsewhere, and with one exception has no real benefit over what's proposed here. But it does have a big downside in that it encourages explicit use of nil comparison instead of unwrapping.

The one benefit – the ability to assign a value into the optional within the scope – can hopefully be addressed via some kind of mutating borrow syntax like if var &x in the future. This could also extend to not just optionals but any enum as well i.e. case var .success(&x): x += 1 // mutate value wrapped inside a Result.

(hypothetical syntax here to illustrate concept, we can bike shed this particular spelling some other time)

14 Likes

Are you trying to distinguish between "we've never tried to set this value" versus "We tried to set this value, but we haven't gotten an answer". When I've needed something like this, I prefer a (value, circumstance) tuple. I like this because I can cover things like "no answer", "never tried", "got an answer, let's goooo!", and "got an answer but hold on, we're not done."

So this would be prohibited?

someSequenceOfOptionals.forEach {
  if let $0 {
    // uh-oh, shadowing $0!
  }
}

There’s a reason the if let x { // x is unwrapped } form keeps getting proposed. It’s the obvious choice to many Swift programmers. The modification of adding ? is a concession toward consistency and clarity at the point of use.

Yes, and for good reason. There’s actually a much simpler option in that scenario:

Lazy method composition means chaining operations instead of nesting them. Where you can’t use method composition, you’d probably benefit from the explicit names.

Let me give an example of one of those occasions, and why I’m opposed to implicit shadowing:

someSequenceOfOptionals.forEach { element in
  if let element {
    // element is `Element`
  } else {
    // element is `Optional<Element>`
  }
}

I feel that’d be extremely easy to overlook. The shorthand arguments for closures (and optional binding, in my proposal) are unambiguous by virtue of being impossible to shadow and impossible to use as a normal name.

It also avoids this problem:

someSequence.forEach { element in
  if let element.optionalProperty {
    // What even happens now?
  }
}
2 Likes

Oh my, I use if case let ... & guard case let a lot, so this sounds awesome.

1 Like

Thank you for the support, much appreciated.

I think both if let foo { } and if let foo? { } are totally reasonable ways to spell this feature. I'm not especially opinionated on which one is better than the other, and would support either.

Some questions I'm considering when trying to choose between the two: Is if let foo sufficiently clear? If not, then is if let foo? meaningfully more clear?


My personal thoughts on this:

if let foo = foo (the most common existing syntax for this) unwraps optionals without an explicit ?. I think this implies that a conditional optional binding is sufficiently clear without a ? to indicate the presence of an optional. If this is the case, then I don't think an additional ? is strictly necessary in the shorthand if let foo case.

While the symmetry of if let foo? with case let foo? is quite nice, I think the symmetry of if let foo with if let foo = foo is even more important. Pattern matching is a somewhat advanced feature — if let foo = foo bindings are much more fundamental.

5 Likes

I think the postfix ? is one of the most overloaded operators in Swift, so I don’t think it makes anything clearer.

Do you have any thoughts on the non-shadowing variation I proposed?

Speaking personally, I think using $0 for this would be a bad idea. $0 is only useful because it enables extremely terse closures. These are good in moderation when they are so short and simple that it's obvious what is going on. But as soon as your closure gets more than trivial, you should name the arguments.

But when you do, those arguments are at least not repeated in a way that adds noise. It is a.reduce(x) { result, element in ... }, not a.reduce(x) { result = result, element = element in ... }. In a way, this proposal aims to achieve similar clarity and concision for if let.

  if let element.optionalProperty {
   // What even happens now?
 }

There are two very reasonable answers: either this declares a local variable called optionalProperty, or it doesn't compile. Both would be fine IMO.

11 Likes

This sort of exists now, in the form of Optional.map{…}, which is a very useful tool, but one I find myself resorting to pretty rarely these days, because it doesn’t seem to be particularly popular in terms of readability.

1 Like

That’s true. I use it quite frequently, though usually just for brief statements or method composition.

Kotlin does something fairly similar to this when it comes to casting (called smart casting).

Basically you can do something like this:

var attribute: Any = “test”

if(attribute is String) { 
    // in this scope attribute is a string
}

If you ask me this behavior is much nicer than the swift equivalent:

if let attribute = attribute as? String {

}
1 Like

This has been broached above:

Any is bad and should be discouraged wherever possible. It is literally the worst-case scenario in terms of optimization and usability.

It should never feel nice to use.

What is this comment in reply to, and based on what are you claiming Any “should” be discouraged?

I don't support this, as it makes the code much harder to reason about at the point of the change. E.g. consider this:

func foo(bar: Int?) -> Int{
    if let bar = bar { // !
        return bar + 1
    }
    return 0
}

The marked line is clear. The first bar is of type Int, and its scope is limited to the closure following. The second bar has type Optional<Int> and its scope is the whole function. You can get the type of either. If you click on either in XCode you see the other instance of each highlighted etc.

func foo(bar: Int?) -> Int{
    if let bar { // !
        return bar + 1
    }
    return 0
}

Change it to the above though, and now what type is bar in the marked line? What should be highlighted if I click on it? What should happen if I find instances of that symbol, or refactor it or the other, differently typed, appearances of bar in that function? Obviously this is trivial function, but a more realistic one could be much more difficult to reason about.

The motivating example is unconvincing. I don't have any variables like that in my code but if I did I'd take advantage of the pattern to use shorter names which make sense locally, e.g.

let fooLoadedFromSomewhere: Foo? = ...
let barLoadedFromSomewhereElse: Bar? = ...

if let foo = fooLoadedFromSomewhere, let bar = barLoadedFromSomewhereElse {
    ...
}

I.e. there's never any need to write someLengthyVariableName = someLengthyVariableName it can always be replaced with something shorter, which can also make the following code clearer and more compact.

3 Likes

Right. I almost never use Any. This was just the least verbose example I could think of.

But when casting from say MyClass to MySubclass or from MyProtocol to MyProtocolImplementation this would be nice syntactic sugar.

1 Like

I think all of this applies to optional bindings, with precisely the same reservations.

What about nested Optional?
There is an obvious way to handle those, but if let x, let x, let x feels at least a little bit strange (but maybe that is just the usual quirkiness of layered Optionals).
Especially with the "require a question mark" variant, this might be an important question.

1 Like

I think a major issue with using $0 for this is that the primary goal of the original thread (Let’s fix if let syntax) was to allow for descriptive variable names that did not have to be repeated, because the friction of the repetition encourages using less descriptive names.

Using $0, $1, $2, etc. makes the unwrapped value variable names much less descriptive, reducing clarity in the simple case, and reducing it even more so when there are multiple optional bindings, with multiple numbered variables to contend with, or in guard let statements where the scope of the numbered variables would not be bound by an if block.

I also think that the motivation from the original thread could be stated more fully in the pitched proposal. (@cal)

5 Likes

Variable names only need to be long enough to be understandable in context. Swift’s conventions favor short names and extensive use of composition, which means that you can seamlessly drop much of the verbosity in many cases.

I’m not really in favor of making any changes to optional bindings: the current behavior is meant to prompt the programmer to elaborate on the purpose and/or meaning of the bound value. Explicitly shadowing the optional variable is tantamount to opting out of that, just like shorthand arguments in closures.

I don’t actually understand why that would be a common pattern: branching control flow implies additional meaning is ascribed to the result.