`if let` shorthand

Thank you, this is great!

As everyone else has pointed out, this is absolutely the single most requested feature in Swift, if not formally, then informally, so the demand may be there to justify renewing this conversation. This is actually not even the first time I've seen an implementation that closely resembles yours ā€” although I don't know if that other person ever chose to post it publicly.

There is absolutely friction associated with syntax like if let moose = moose; I think the fundamental disconnect between people on this issue tends to be the question of if that friction serves a purpose, and if the role of a language is to remove friction, or if it should guide the programmer toward certain principles.

Swift works very hard to make a lot of things frictionless:

  • It doesn't force you to manually allocate and deallocate memory, or to manually count references
  • It uses dynamic arrays by default.
  • It makes bridging between highly-analogous types with identical behaviors trivial, and sometimes even invisible.

But it also intentionally includes friction in a lot of places where ergonomics could hide subtle, but impactful, decisions someone is making:

  • When you slice Strings or Arrays, you get back sliced types that make you choose to incur the potential cost of copying.
  • When you leave the world of memory and/or type safety, you have to explicitly type out "unsafe"
  • When you are working in try/catch contexts, you have to manually mark the code that throws with "try" instead of marking a whole block.
  • When you use Swift's concurrency constructs, you are increasingly forced by the toolchain to demonstrate that the data you are working with can safely be accessed concurrently.

At one point in my programming life, I was incensed by how nil was handled in Swift. I thought how refining it away was written, and the fact that these types were inherently different, was a pointless form of busywork that just got in my way. These days, I've come to view it (and all its verbosity) as one of my favorite features, because I personally welcome friction if I think it helps me think clearly as I write and read code.

There are sorta two kinds of optional value. First is the kind where nil represents an indicator of consciously inserting nothingness into a situation ā€” for example, saying "I don't want anything to observe this anymore," or "I don't want the fill of this rectangle to have any color at all."

Second is the kind where nil represents a form of uncertainty. Maybe there's a failable initializer, maybe you said try? somewhere, maybe you lost a weak reference, or maybe, when you're looking for the red component of that rectangle's fill, someone could have told your API that they don't want any color there. As it just happens, many times, the obligation to directly cope with a nil in these situations is the obligation to directly cope with a common source of bugs.

That first category is easy enough to deal with, and it doesn't really need any sugar, because usually what you're doing with it is action-oriented enough that you may want to be really stepping through something gradually anyway. The second category is really what a proposal like this is for, and for me, the question of if a feature like this is a net positive in the language comes down to if removing friction from this action makes it too easy for someone to ignore the uncertainty that has been introduced into a situation.

The technical and spelling details of a thing like this can be worked out. Those are fun to discuss and play around with, but they're not the central issue that creates the challenge here. The reason this exact conversation has been so intractable over the last half-decade or more is the philosophical side of this issue, and the question of if Swift should be firmly guiding people and slowing them down in these situations, or if it should be leaving them alone.

8 Likes
Brief tangent on meaning of nil

The ambiguity of whether nil means ā€œthis is known to have no valueā€ or ā€œthis has an unknown valueā€ is a frequent source of frustration when Iā€™m designing APIs.

That being said, I don't think that the standard library itself needs to make that distinction the way JavaScript does with undefined. Such a type would be literally identical to Optional, which is already trivial to implement yourself.

Iā€™d make a simple package that implements a type for unknown values, but Iā€™m completely stumped on the most important part: bikeshedding (Iā€™m dead serious). The entire point is to convey that a value may or may not be known, but simply calling the type Unknown would falsely imply it is always unknown.

If anyone could simply think of a clear name for that, Iā€™d appreciate suggestions.

Personally, Iā€™ve never been particularly upset about having to explicitly shadow optional bindings.

What I do find annoying, though not necessarily to the point I think it is important to address, is the inability to invert control flow unwrapping. There are many occasions where Iā€™d like to exit scope early if a value isnā€™t nil. Iā€™d prefer to use guard for the purpose, to make it easier to understand at a glance, but optional bindings canā€™t be used in an else block.

Mind you, I have no idea how youā€™d express that without causing readability issues.

2 Likes

I'm highly in favor of this. The autocomplete support in Xcode is nice, but isn't perfect.

My only real concern is how new developers to the language would understand this. A pretty common misunderstanding I've seen for Swift is how if let actually creates a new variable. This might make that just a little bit worse, but the solution to both is really to introduce optional unwrapping in tutorials by using different names to make what's happening obvious.

I'm highly in favor of things like this in the rest of the language. Swift feels halfway to being succinct. Certainly a big improvement over ObjC, but there are still a lot of places where things have to be duplicated. That hurts readability. It's like reading a legal document that spells out a term in full every time it is used. I'd love to see some of the shorthand from typescript/javascript make it's way into Swift.

There is a more generic way using zip function:

zip( foo, bar )
    .flatMap { nonOptionalFoo, nonOptionalBar in
        ...
    }

More verbose but has the same shape for other types that have flat map defined (eg. Either, Promise, Future, etc.).

Unfortunately at this point in time, you have to write this function yourself. Or use a library that has it eg. OptionaAPI.

Will this also support:

if let foo as? Bar { ... }

as a substitute for:

if let foo = foo as? Bar { ... }
5 Likes

This seems reasonable to me, but isn't currently included / implemented.

1 Like

Initially I was completely in favor of this proposal but I'm starting to think that just writing let foo may hides the fact that we are creating a new variable and this can be confusing. I'm wondering if instead we could introduce a unwrap operator that allows us to direct use a var/let of type Optional<T> as T inside a given scope if is not nil.


let foo: Int? = 1

if unwrap foo {
   // foo as Int, equivalent to foo!
}
2 Likes

One thing for naming / title of this pitch & proposal:

While

'if let' shorthand

is catchy and easy to understand,

Shorthand for variable shadowing

or

Syntactic sugar for variable shadowing

might describe what this pitch is actually intended for more properly, as this will be applied to not only if let but generally all the unwrap declarations (but only when shadowing) like described in proposal:

This would apply to all conditional control flow statements

1 Like

If you generalize it to "variable shadowing", you need to answer questions about whether something like this is allowed:

func use(_ thing: Thing) {
  var thing
  thing.mutate()
}
2 Likes

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