`if let` shorthand

Not to comment on other parts of the reasoning, nor the opinion being expressed, but I loathe the "Just don't use it" argument with passion:

If it's really that good of an idea, then we can do better.

9 Likes

Dismissing other participants' reasoned objections as being due to bias is hardly helpful, especially when you then admit you don't understand all of their objections. And "just don't use it" is not helpful either; this is a core language feature so even if a programmer swears off it they will encounter it in library code, in co-workers' code.

3 Likes

I think they're actually admitting that it is biased to use this thread as the only piece of evidence to conclude "opinion a) is a minority".

8 Likes

Thinking (just a little) more about it, and given:

  • The if let x = x { / guard let x = x else { use cases are ultra-frequent.
  • We can afford ultra-sugaring an ultra-frequent use case.
  • if var x = x { is less frequent.
  • I don't buy the argument that we must ship if var x { along with if let x {. Will we ship if ref x {, or if inout x { as well? IMHO, these should be pitched separately.
  • I'm not sure people want exactly if let x {. Maybe they mainly want to avoid the repetition of the variable name, and a very short yet legible syntax.

So I suggest dropping the var variant from this pitch, and just ship:

if x? { ... }
guard x? else { ... }

Short, sweet, and to-the-point: if let x = x is sugared to if x?.

  • if x? may be easier to understand by non-Swift-literate people (assuming they don't meet an optional bool too soon, and did not code in Ruby).

  • if x? brings back the question mark and makes case let x?: less odd (in this case the let is necessary in order to disambiguate between binding creation and matching with an existing binding).

If we want to support var or other modifiers in the future, we'll be able to support if var x? {, as well as if let x? {. The sugar chain will then read: if let x = x can be sugared to if let x?, which can be sugared to if x?. But this will live in another pitch.

1 Like

I worry that this could cause confusion. if let x = x makes it clear that a new binding is being declared. if x? does not. With if x?, I think a user would expect this to work:

var x: Int? = 42

if x? {
  print("We're done with \(x)")
  x = nil // Marked line
}

If if x? is shorthand for if let x, then the marked line above must fail to compile. I think we're going to do a disservice to readers of Swift code if we allow more ways declaring new constant declarations without let, especially when it shadows an existing declaration.

9 Likes

You may be right. But we are discussing a sheer swiftism. It is uncommon in other languages, and it packs a lot (optional unwrapping plus binding creation). But it is also ultra-frequently used. So I'd rather bet that its behavior is rather quickly fully mastered, regardless of its little inconsistencies.

Agreed, this is why I personally think its important to include let in any shorthand spelling.

11 Likes

To me, removing the let has more advantages than caveats.

First, it voids all discussions and worries about var. var is not the point. People ask for a enhancement to if let x = x.

Second, it answers precisely and radically the requested use case.

Third, it does not, to my knowledge, hinder other language features. It is so small, so focused!

Fourth, if x? restores the question mark that some people were missing.

Fifth, I don't buy that binding creation must be visible. They are not in function arguments. They are not in closure arguments. They are not visible in highly optimised areas of the grammar.

And aren't we aiming at highly optimizing the grammar in this thread?

That's all true. We also don't use let in for loops, as was discussed upthread. However, none of these sites are intended to implicitly shadow an existing declaration. You could choose to shadow an existing declaration with the for-loop variable name, but that would be an explicit choice. Here, we're discussing a feature whose only purpose would be to shadow existing variables. That feels different.

16 Likes

I guess we can agree that this is becoming very subjective. And I listed other arguments - including the fact that the let can be reintroduced on a second step if deemed necessary.

I hope the if x? suggestion can at least help this thread make progress by refocusing on the main point of the pitch (which is not about if var).

And reading how @Ben_Cohen looks eager to make progress in this area, I'm quite curious of his opinion on dropping let and introducing the question mark.

I agree this is an important distinction. As I mentioned above, I think the lack of let in the analogous capture list syntax (e.g. { [x] in ... }) actively hinders the user's understanding about what a capture list is really doing (i.e., creating a new, immutable copy of the captured value), and I think it would similarly hinder understanding in this case.

I don't think we can cleanly separate these out. The decision about what syntax to use is, in my view, inexorably tied to what forms we will want to support. It would be a shame to launch with the if x? { syntax only to realize in a later version that users really do want to support var (ref, inout...). Then we'd need to continue supporting the 'bare' syntax in addition to the syntax with introducers.

Now, maybe it would be the case that we want to support the 'bare' syntax anyway (regardless of support for var et al) since let is so overwhelmingly common. But since we're talking about sugar for if let x = x I think this discussion should consider which generalizations of that construct the new syntax should support, and not summarily discard such considerations as out-of-scope.

6 Likes

Just for the sake of argument, why shouldn't it work this way? If I were to write this instead, then it would:

var x: Int? = 42

if x != nil {
  print("We're done with \(x!)")
  x = nil // Marked line
}

So.. why not just make this syntactic sugar for that sort of pattern where the x effectively changes from type Int? to Int! when referenced inside the if body and behaves accordingly. If x was a let x instead, it'd still "just work" with this same syntax except you can't assign to it and you'd get a suitable error. I don't see why it has to re-bind to a new variable behind the scenes.

This concern was addressed in the last paragraph of `if let` shorthand - #207 by gwendal.roue

1 Like

Right, but I'm trying to emphasize that IMO, supporting both if x? and if let x? is actively undesirable. If the language already supported if let x? I highly doubt there would be sufficient appetite just for dropping the let, so if we start by allowing if x? only to discover we want to support var et al later, we'd end up with a vestigial piece of syntax simply because we didn't consider the larger picture at this stage of the evolution.

4 Likes

I can hear that.

And yet we have the crowd of trailing closure syntaxes, from { (x: String, y: String) -> Bool in return x < y } to { $0 < $1 } with many steps in between.

So if I agree that if x? is bold, I'm also aware that I'm not a professional language designer, as many people here. I'm quite curious to hear the opinion of people of are not afraid or sounding like they break rules, and can pounder the advantages I listed against the drop of let.

I don't understand. peel is not intended to replace if let x = x?.y?.z. Is if let z = x?.y?.z { } the statement you meant?

I don't think it is particularly useful to use peel to rewrite
the statement,

if let z = x?.y?.z {
}

but we can do it in a couple of ways,

if peel? x, peel? x.y, peel? x.y.z {
        // in this scope, x, y, z are copies of, respectively,
	// x!, x!.y!, x!.y!.z!
}

and

if peel? x?.y?.z {
	// in this scope, z is a copy of x!.y!.z!
}

The latter way saves a couple of characters. If z had a longer name,
such as theInventionOfSpacesBetweenWordsEnabledFluentReading, then the
savings would be substantial.

Dave

There might be something here... The case of weak variables must be taken into an account, but if there is some syntax:

var x: Int? = ...

if ... some syntax here TBD ... {
  x.foo() // x is not optional here
  print("We're done with \(x)") // x is not optional here
  x = nil // this changes "x" of the outer scope!
}

that could be quite convenient. It's sort of "half shadowing", where you shadow a variable with a non-optional version for the "read" access and don't shadow it for the "write" access. What best to put in "some syntax here TBD" for this syntax to work don't know but it shall probably not have let/var.

1 Like

We can't just assume that an expression which started out non-nil will remain so for the duration of the block.

class Person {
  var jobTitle: String?
 
  func f() {
    if jobTitle != nil {
      // Okay, jobTitle is non-nil here
      quitJob()
      // Now jobTitle is nil! So we can't just treat 
      // `jobTitle` as non-optional for the whole block
    }
  }
  
  func quitJob() {
    jobTitle = nil
  }
}

By binding the value to a new variable, we guarantee that even if the original variable was set to nil during the block, we still have access to a non-optional value for the entire life of the if let block. Otherwise, you get variables changing type on us halfway through a block.

9 Likes

I wasn't assuming that? The example didn't assume that, either - it changed the variable to nil itself, after all. Obviously code that comes after needs to be aware - hence the idea that it's just sugar for changing the type to being forced-unwrapped which is already a thing in the language people are familiar with.

The question is, which part of if let lhs = rhs to be omitted.

// omitting LHS
if x? {}
if unwrap x {}
if peel x {}

// omitting RHS
if let x {}
if let x = _ {}

I think omitting LHS leads to confusion (e.g. why can't we do if self.x?)

2 Likes