`if let` shorthand

I disagree. There are no questions about the borrowing semantics of if let x that do not also exist about if let x = x, and the latter is already valid Swift.

2 Likes

What if bjhomer's quitJob() function was defined in another module, and the compiler had no visibility into the implementation?

Is borrowing applicable in the (important) case of weak references?

weak var x: Obj?
......
if let x = x {
    x.foo() // without fear
}

I think I accidentally confused things again by pulling two different concepts into one thread. I'm not specifically arguing for unwrap here, I'm arguing that it "might" be the right thing, but we can't evaluate that right now because we don't know what the design space is for a holistic model is.

This is I raised the issue about the ownership model - we know it is coming soon and know the general shape of it but the detailed design isn't nailed down yet. It is very important (in my opinion) for the long term evolution of Swift for us to have a consistent model that scales across it, and use syntactic sugar to nudge Swift programmers towards the correct defaults.

Given we've gone ~7 years without this, I don't see the downside of waiting 6 more months to evaluate a proper design.

-Chris

15 Likes

Right, I understand that this is definable and has obvious semantics, I know how if let works.

The power of sugar is that "making something easy" encourages its use by biasing programmers towards that. Sugaring the "wrong thing" can add language complexity, and it can also lead to people doing things that aren't aren't best practice (e.g. because the correct thing isn't sugared).

Simple example: one of the major problems of C++ is that it gets all the defaults wrong. Forgetting to delete a copy constructor can lead to bugs (Yes I know that is more severe: the discussion in this thread would "only" lead to reduced performance predictability).

-Chris

7 Likes

Some thoughts: if you assume that the ref and inout introducers discussed here will be added to the language, it feels very natural for this style of shorthand syntax to support those introducers as well:

// "Existing" optional binding condition syntax
if let foo = foo { ... }
if var foo = foo { ... }
if ref foo = foo { ... }
if inout foo = &foo { ... }

// Shorthand syntax
if let foo { ... }
if var foo { ... }
if ref foo { ... }
if inout &foo { ... }

In the same way that let / var introducers are currently interchangeable throughout the language, I'd expect this to be the case with hypothetical ref and inout introducers as well -- all of these introducers will likely always have reasonable, distinct use cases (right?). It seems like we would want to preserve that consistency and support all of them here as well.

One difference is that the new introducers would enforce exclusivity (right?), so in the future it may make sense to permit something like:

// foo.bar is optional
if inout &foo.bar {
  // foo.bar is non-optional
  // this is safe because the inout borrow guarantees exclusive access
}

Supporting this for inout mutable borrows doesn't necessarily mean that also supporting a more limited form of this feature for let / var is harmful. As long as let and var exist, it seems reasonable to permit authors to use them in an ergonomic way.

4 Likes

Good point – probably not. One of the reasons borrowing is more efficient is it doesn't need to bump a reference count, but this reference count bump is what strengthens the weak reference. So this is another case where if ref isn't always the right default, and instead users need the menu of all four options which can all be sugared consistently with this proposal.

3 Likes

One thing to note is that borrowing a property will still go through its usual stored, get/set, or read/modify interface to access the value of the property. Weak properties are effectively computed properties whose getter and setter capture a strong reference to the object at the point of access, so if ref of a weak property would be effectively no different from if let, "borrowing" the gotten temporary strong reference instead of assigning it. Unlike if inout, which would serve a currently-impossible use case, I suspect if ref would have much more limited applicability working with copiable values in Swift, since an if let value's lifetime is generally obviously locally eclipsed by the lifetime of the optional it was bound out of, and we can borrow out of the optional storage already.

5 Likes

(All my opinion and not hard facts, since that seems a necessary disclaimer:)

I disagree. Introducing more introducers in place of let/var should work reasonably well, and the proposed shorthand does offer a natural place to slot them in, but mitigating the potential confusion (esp if the new introducers are not used that often) relies entirely on the surrounding syntax:

If I see ref x = y, even though I don't know what ref means, I can still recognize the "shape" of a declaration/assignment. I see a keyword, a new identifier, the crucial bit: an equals sign, and then whatever goes in. Even without any knowledge of ref, I can reasonably deduce that something similar to let or var must be going on here.

If, on the other hand, I see just if ref x, there is no shape to guide me to recognizing it as a declaration/assignment. I, knowing nothing about ref, might just as well take it to mean "if x is a reference in some way I don't know about". Same applies to inout.

The same applies even to the proposed shorthand for just let and var, although it is perhaps less severe thanks to the fact that due to their pervasiveness in other Swift code, even novice users will quickly associate let and var with declarations.

And this is just the part about it being a declaration/assignment. The fact that optionality is somehow involved is completely obscured, relying only on first having learned about if let, and then recognizing the very distant similarity of if let foo = bar to if ref x, and this part is a problem with the shorthand even ignoring new introducers, and, tbh, even with the current if let syntax.

4 Likes

Right, but you had to learn the shape of assignments in Swift, and of optional unwrapping. This mainly seems like an argument from experience, not something generalizable to the language. There is no natural syntax for these operations, all of the forms have to be learned. If a user is taught "Use if let x = x to unwrap an optional", the natural follow up is "If you find that tedious, you can use if let x to unwrap into a value of the same name." This would apply to new keywords as well (though I kind of prefer if they were just versions of let and var: let(ref), var(inout)).

1 Like

I would also suggest not getting hung up on the specific keyword ref, which is just a placeholder suggestion for the concept of a shared borrow binding. The exact keyword would end up getting extensive discussion, into which could feed the desire to make it sit well with this sugar.

7 Likes

May I ask for an elaboration, directions such as this: A roadmap for improving Swift performance predictability: ARC improvements and ownership control might (potentially) change or affect how if let or guard let etc. behave – So it is worth waiting to see that settled first... then this topic next. Am I in the right direction?

At least this thread proves once and for all that the amount of energy people spend debating an issue is inversely proportional to its importance.

5 Likes

How about if some foo { ... }. It's consistent with the .some case in Optional. The fact that let unwraps is magical and can be unintuitive. Teaching young programmers who aren't familiar with Optional I've always had to tell them "this is what let does here, it unwraps, you just have to know it. Yes, that's inconsistent with what let does outside of if and guard statements.". But if one teaches Optional from its cases none and some(Wrapped) then if some foo { ... } makes a lot of sense.

Only issue is the word some is being used to refer to protocols these days as well, like some View, but I think this is a different context.

3 Likes

Wouldn't that beg the opposite case, like if none foo (i.e. if foo == nil) as well?

I wouldn't argue against it if people liked that. But in my opinion, comparing foo to nil or .none with == is intuitive and simple enough, unlike using let to unwrap (not intuitive) or foo = foo (not as simple as it could be).

My suggestion of if some foo is just an alternative to the previous suggestion of if unwrap foo.

I like the if some foo syntax. I worry that reading Bool types will be confusing for someone learning the language.

var isImportant: Bool?

a thousand lines later…

if isImportant { majorDamage() }

Did you mean to post this in the review thread?

It would be nice to add a case for case ... where let:

func foo(action: String, userId: String?) {
    switch action {
        case "bar" where let userId:
            bar(userId)
        // ...
    }
}

func bar(_ userId: String) {
    // ...
}

At the current moment, this case can be solved in two ways:

  1. One of them that reviewers don't like:
case "bar" where userId != nil:
    bar(userId!)
  1. Another option is to simply use if:
case "bar":
    if let userId = userId {
        bar(userId)
    }
1 Like

The seems useful. It's worth noting that switch cases don't currently support optional binding conditions. For example, this doesn't compile today:

func foo(action: String, userId: String?) {
    switch action {
        case "bar" where let userId = userId: // πŸ›‘ error: expected expression for 'where' guard of 'case'
            bar(userId)
        default:
          break
    }
}

func bar(_ userId: String) {
    // ...
}

I'm not really sure why switch cases don't support optional binding conditions. If we added support for them in the future, we would definitely want to support the shorthand syntax as well.

3 Likes