`if let` shorthand

For me, it reads ambiguously because foo could be a Bool. I need to figure out the type of 'foo' before I understand whether the if statement is evaluating foo or creating an unwrapped constant foo. In the for-in case, it's not ambiguous.

4 Likes

I think this looks a bit broken at first. Defining a let without assigning anything to it seems odd to me personally. But I guess I could adopt to it if I have to.

It could even be an Optional bool :wink:

Presumably these ought to be else if.

1 Like

Haha, oops! Fixed, thanks :+1:

2 Likes

@cal

Happy to see this going forward.
Because I never omit type declaration, may I ask if this expression allowed with current implementation?

var someLengthyVariableName: SomeLengthyTypeName?

if let someLengthyVariableName: SomeLengthyTypeName {
    ...
}

Yes! That's supported by the current implementation.

1 Like

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 {
}