`if let` shorthand

Ouch. That's right.

If we drop the assignment, we might as well go the Typescript way:

if foo {
// foo is unwraped here and ready to use
}
//or
if foo as? Bar {
}

I don't think this will be a big issue in practice; if a user tries to mutate a variable declared with if let foo, then the compiler will produce a warning. And besides, the entire purpose of the let and var keywords is to explicitly declare new constants and variables. While being able to modify the optional's value within an if statement may be useful in certain situations, I don't think it's useful enough to warrant its inclusion as a language feature. Besides, this limitation is easy to work around:

if var wrapped = optional {
    defer { optional = wrapped }
    ...
}

I don't think mtsrodrigues was commenting on the mutability of such a reference.

Additionally, if if let x { is added to the language, if var x { would also be added. There's no logical reason to introduce the shorthand for one and not the other.

4 Likes

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