`if let` shorthand

I agree it’s not that of a issue (I was just trying to see it with the eyes of someone not so familiar with the language). But this new way to unwrap an optional in a given context would allow us to do what we want most of the time when doing the if let foo = foo dance, which is access Optional<T> as T when it's safe, while giving the bonus of being able to direct mutating the original variable if desired. It would be an alternative way for when we don't necessarily want to create a new variable to just access a existing one.

There is a superior way to implement that concept: generalize the recent proposals for advanced memory management.

Logging in for the first time in years to say yes please, I fully support this. I like let more than unwrap because it indicates the creation of a new variable.

3 Likes

I would just like to quickly remind everyone that one of the principal objectives of Swift is clarity at the point of use, even at the expense of brevity.

I’ve given this some thought, and I don’t think it is a good idea to implicitly shadow an optional binding. However, that doesn’t mean that there couldn’t be shorthand for the purpose. In fact, I think there’s already an excellent precedent.

if let optionalValue {
  print($0)
}

How’s that for shorthand?

Of course, the same rules that apply to inline closures would apply here: no shadowing. While I often get annoyed by that limitation, I can’t deny that it is an important one.

In addition, it would be all or nothing: either every optional binding is explicitly named, or none of them are.

Thank you so much for working on this. This general idea has been floated several times, and I think it would be an important and valuable addition to Swift. This particular bit of sugar is much more common than many other (necessary) bits of optional sugar already supported by the language.

I think the one area for debate is whether this feature should include an optional i.e. if let foo? { }. My personal preference is to include the ? as it helps make the fact that this is unwrapping an optional nice and clear when reading the code.

The previous opposition to this has been mostly "that's another new use of ? which already does a lot". On the other hand, I feel like while it isn't exactly the same, it echoes the switch usage of case let x?:.

This syntax might also then be extended to support unwrapping of enums in general i.e. if let .success(x) { } (again echoing the case syntax).

29 Likes

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