`if let` shorthand

I believe allowing this would give developers an easy path to sacrifice clarity for brevity, and so should not compile.

In some very common cases, such as the first property of collections, the automatic name would almost never be a clear one, but would definitely be shorter to type:

guard let customers.first else { return }
// 'first' is the easiest name, but almost never a good name
print(first.firstName, first.lastName)

It also sets up the potential of conflicting implied names, for example:

guard let budgeted.total, let actual.total else { return }

or

guard let locales.first, let languages.first else { return }

Where the shorthand works on the first use but not the second.

The compiler could potentially work around this by creating up an automatic variable name such as budgetedTotal, actualTotal, localesFirst, and languagesFirst.

In some cases, that can lead to a descriptive variable name, in some cases like localesFirst, not a great name.

Finally, the construct if let locales.first is not declaring something called locales.first, whereas the shorthand as pitched, if let foo, is declaring a new variable foo, so stays true to the meaning of let. It's the right hand side of the current expression that is omitted with the shorthand, not the left hand side.

So, I hope this usage would be disallowed.

9 Likes

You're exactly illustrating the point I'm trying to make. Because you are anchored on the "if let" syntax, you go to "of course" we should support the "if var" analog.

That isn't clear to me at all - I don't see how "if var x {" is a good thing to support: it is transparently introducing new mutable shadowed copies of unwrapped values with extremely tight syntax that is easy to overlook. This wouldn't be an "inout" binding to the element with the optional, which would betray a lot of expectations and introduce a lot of bugs.

Perhaps I should state my position from a different direction - we should decide if we want "if var" first. If not, then "if let" is clearly the wrong syntax. If we do want to support "if var" sugar, then I agree it makes sense to introduce the pair of "if let" and "if var" sugars.

-Chris

13 Likes

I don’t follow this logic. I wouldnt think it unreasonable to limit this extremely targeted sugar to just if let.

3 Likes

Thank you for restating this, it definitely clarifies things for me.

From what I can tell optional binding to a mutable variable has been available since Swift 3, so for about five years.

Has there been a lot of confusion, difficulty, or misuse of the existing if var x = x syntax?

7 Likes

I don't think there's confusion about var on optional-binding-condition clauses. However, there was confusion on var for function parameter position (SE-0003), which, to me, looks very similar to the var version of this pitch.

3 Likes

I'm not making any statement about whether the if var x = x syntax is confusing. I'm suggesting that a new if var x { syntax could be. I'm not suggesting that we remove if var x = or guard var x =!

More generally, I suggest going back to basics on "why do we sugar things" and ask whether the rationale applies to "if var".

The goal of Swift language design isn't to minimize number of characters in code, it is to find a balance between "expressivity", and "readability". Readability isn't just "what does this line of code do" it is a deeper "what is the lifecycle of maintaining and evolving a codebase, particularly when it is worked on by multiple people, or the project spans years of development".

We could sugar lots and lots of things - there is ample grammatical space to introduce new things into the language - but each new thing we introduce is something that Swift programmers will be expected to know/learn when they encounter them in a code base. Further, we have to consider what the implications are for the larger maintainability of the code base.

In the case of if var x {, my understanding is that if var x = x { is FAR less common than the corresponding if let x = x { pattern, because it is extremely uncommon to make a mutable copy of something inside of an unwrap. Given that, the benefit of sugaring this is very low.

At the same time, the cost of confusion is potentially very high. Both because some people will expect that this has something akin to an "inout" binding to the underlying value (when the original optional value is mutable). This confusion isn't possible with the 'if let' pattern because it is an immutable binding.

Am I wrong that if var x = x { is uncommon in swift code today?

-Chris

17 Likes

A small comment on this, but this is an extremely dangerous way to approach language design, or design in general. If Swift followed the "give the customer what they are asking for" approach to design then we would be enjoying "everything is an object/class", "messages sent to nil are implicitly silenced" and many other "convenient" things that undercut the goals of the language.

While it is extremely important to listen to the problems and be open to new ideas, it is just as important to understand the framework, the long term direction of what you're trying to achieve and be willing to consider what the best way is to "solve the problem the customer is facing" even if it comes with a different solution than what they naturally gravitate to.

Given your specific example, sometimes you need to understand that your grass is going to be torn up with dozens of ad-hoc walking paths, and you should predict this and be willing to invest in proper infrastructure like bike paths etc.

-Chris

22 Likes

The system is already designed. People are already using it. The only question here is a minor convenience to the spelling.

It’s like laying out a rectilinear grid of sidewalks, and observing that after several years one particular corner is so heavily taken that a small diagonal path has been worn across the grass.

And then when someone suggests “Maybe we should make a little diagonal section of walkway there”, you say what?

“It’s extremely dangerous to give people a path where they want to walk”?

“Some people might think the path goes in a totally different direction”?

I think you’re making this into something way bigger than it actually is.

• • •

if let x {” is so clear and so expressive, that people keep asking, year after year after year, why on earth it doesn’t work already. Why it wasn’t part of the original language when Swift was first released. It’s such a natural spelling for such a common concept, which fits so cleanly into the rest of the language, that not having it makes things feel somehow “off”.

If people completely new to Swift find themselves consistently expecting this spelling to work, and they all understand exactly what it should do, then what on earth are we trying to accomplish by persisting in directly contravening their common intuition?

There is zero danger of confusion here. The whole reason this feature keeps being proposed, is that it is such a glaringly obvious thing to have.

And by consistency, that extends to both the guard and var versions as well.

Introducing a whole new keyword for this seems like using a sledgehammer to tap in a nail to me. It would also be strange to introduce the term unwrap for this one specific form of unwrapping.

"A new concept" is a fairly nebulous term but I can't see how this can be described as such. It's simple sugar that if let x = x can be written if let x. There are plenty of other places in the language where you can omit "obvious" things to reduce noise.

It's a good thing to support (and in keeping with what if let x would desugar to) for the same reason if var x = x is a good thing to support. Sometimes it's useful to have a mutable copy, and having to declare and assign it on another line is unhelpfully verbose.

This seems like an exaggeration. The var in if var x is pretty obvious. I don't think the claim it would "betray a lot of expectations and introduce a lot of bugs." is credible. Does if var x = x do so? I don't believe so, and I don't think dropping the = x materially changes that.

21 Likes

My problem with it is implicit shadowing. I can’t think of any other example of that in the language (consider the way initializers often work, with explicit self assignment), and it comes with obvious readability pitfalls.

And I still don’t understand why this is helpful. Should this be done for for-loops too?

1 Like

My preference is for "if let x", but "if unwrap x" isn't bad either. Here's some code to get a feel of it:

if unwrap x {
   print(x)
}
if unwrap x, unwrap y, unwrap z {
   print(x + y + z)
} else if unwrap u {
   print(u)
}

I think it reads well. It might be less clear that you're creating a new variable with this (compared to if let), but this might not matter much if that variable is immutable and is shadowing the previous one.

The only (potentially surprising) situation exposing it's a new variable would be:

if unwrap x { // x is self.x here
   self.x = 1 // x is different from self.x here
}

Unfortunately, I don't think we can forbid this while keeping the model reasonable. This is why I'm hesitant to let go of the let (that signals a new variable) and replace it with unwrap.

1 Like

When ownership comes in, we will have borrowed values as arguments. It will be extremely natural to want to project a borrowed optional into a borrowed underlying element, exactly like "if let" does, but without the copy.

Is there a proposal for how this will be handled?

2 Likes

The performance roadmap has a start. Maybe if ref, or whatever it ends up being called?

Changing the subject back, I firmly believe that explicitly naming optional bindings is important for code clarity. And if there’s disagreement on that front, it would be better to use shorthand arguments ($0, etc.) instead of implicit shadowing. It’s not like the latter would provide any further information.

1 Like

It depends what you mean by uncommon, but I searched a few large projects and it appeared several times in each one, and all the cases I checked looked legit. It's obviously a tiny number compared to if let but I'm not sure if that's relevant. The if var variant would be consistent, harmless (or at least not differently harmful to the existing desugared syntax), and useful.

5 Likes

Right, I'm not arguing whether they are correct or not (surely var pattern bindings are useful!), I'm asking whether they are important to sugar. It sounds like the answer is "no", that sugaring them doesn't provide an important ergonomic benefit.

I come back to the harm of sugaring them. People seem to be content stating that "it is obvious what if var x { is defined by analogy to the existing syntax. But no one seem to be addressing the problem of "what does a reference to x mean in a scope mean in the face of this"? Changing x from immutable to mutable - or to be a mutable copy of mutable data that isn't tied its original value - isn't something you want to make lightweight and nearly invisible in source.

You also don't want to make it impossible - it is a useful thing (see above) but making it less visible isn't beneficial in my mind.

The reason I asked about the borrow version of this is that we're about to embark on a massive expansion of the type system for ownership. That expansion will need to answer all the same sorts of questions that we've had in the "copy things" part of Swift. Given that we've lived for years without sugaring this, it seems worthwhile to me to get the ownership support further along, to make sure we can come up with a coherent solution to works well with it as well as with Swift 1 features.

-Chris

9 Likes

if var x = is very uncommon; according to a quick GitHub search there are about 50 to 100 "if let x" for every "if var" usage (and in many cases "if var x = " usage is unjustified and is just a sloppy coding). I'd speculate "if var x = " is less needed than an ability to have a "var x: T" function parameter - something we had in original swift but then removed.

...

Nevin: I am sure you mean well, but I would find your arguments more convincing if you addressed the points that I and other people are raising, instead of making undefended unilateral statements/claims that they are just wrong.

-Chris

5 Likes

Given how idiomatic Swift operates, I’d go so far as to say that var in general isn’t that common.

After all, value types are (more or less) inherently immutable, such that “mutation” is really just “replacement”. Add lazy method composition, and a lot of Swift might as well be Haskell.

It’s not the syntax that’s the problem it’s the semantic meaning. if var x=x is a special case of if var closureName = someVariableName. That’s syntax that you can use coherently in something like:

if var counter=someVariable{
      [do stuff with the counter here]
       return counter}

*obviously with the counter having a name that is descriptive of how you’re using it in the closure

But I struggle to imagine a situation where creating a mutable shadow makes semantic sense. I’d love to see motivating examples here.

2 Likes

Let's please avoid those kinds of generalisations and disparagements. It's a feature in the language - if people want to use it, they are very well entitled to do so.

It is actually a compiler warning if a var is not mutated, so presumably those cases are still examples of correct use.

7 Likes