`if let` shorthand

Fair enough. To clarify, I’m just trying to be pragmatic. There are numerous parts of the language that I “just don’t use” that I’m sure other people find useful, for example if var, if case let, and many others. But I would not argue that they shouldn’t exist.

I understand the reasoning is not satisfying and to be honest, it’s not even supposed to be an argument (I think the arguments for this syntax have been elaborated on enough in this thread).

Somewhat tangential: optional binding doesn't currently work in the where clause of a for loop. But if that were added, this shorthand might create a nice way to iterate over a sequence of optionals. Like:

for value in optionalValues where let value? {
  // Body only executes for non-nil values
  // and `value` is non-optional here
}

You can already do this:

let a: [Int?] = [1, 2, nil, 3, nil, nil, 5]

for case let x? in a {
    print(x) // Prints 1, 2, 3, 5
}
4 Likes

The problem I have with if x? {... is that an important thing is happening implicitly, and this particular syntax is still an advanced swiftism.

I try and think about all coding in terms of how I would explain this to someone who knew no programming at all.

Sure, it's easy enough to learn, if you are of the mindset that these symbols are arbitrary and whatnot, but from the view of a typical English speaker, if x? { reads as an unfinished question.

Even my suggestion of if x != nil { x! ... is almost too terse, and doesn't lean on any previous semantics at all, so it's 99.9% arbitrary, and therefore extra slope in the learning curve.

1 Like

I'm seeing a lot of if not a conditional { constructions. One of the things I really value about swift is the step away from the famous C construction of if x = getAThing(). I value the demand for explicitness.

I see these proposals as stepping back to that.

3 Likes

I appreciate everyone chiming in but ultimately I feel that the fact we are 226 post in and everybody has so big different opinions is an indication to me that this is a more complex space than just adding ’sugar’.
When adding something so minor creates such big and spread issues I wonder if it’s not a telling that is not a good idea.
I still haven’t seen a good option and I’m starting to wonder if it would be better to overhaul the entire thing and get it right from the roots. Probably bot useful feedback I know.

So it seems to me that those opposed to sugaring this syntax could "just not use it" (and enforce that by lint rules if desired).

I hope we don’t take this stance tho. Adding more ways of doing the same thing is not something that can’t be just ignored. Every language feature has a cost even if you don’t use it.

5 Likes

Despite the evolution process' stated ideals, it's hard to actually reach consensus on a pitch, especially for sugar. I don't think anyone's made a compelling case for one of the simpler alternatives, so I think you're good to go as is.

2 Likes

It appears that there is some concern that I'm trying to side track the discussion with "forward-looking criticisms" relating to ownership. Perhaps the problem is that I'm not be explicit enough about my concern, so here's an attempt to fix that.

Part of my concern with the framing of this entire discussion is that it is of the form: "if let x = x { is obviously redundant, happens all the time, and there is an obvious sugar: obviously we should just do it".

I can see the apparently simple truth to those statements, and I've raised a concern about var, but let me raise a much bigger issue: ignoring syntax, the current if let semantics are the wrong thing to sugar. The only reason this is common is because Swift doesn't have the "right thing" yet. In my opinion, it would be much better to sugar the right thing rather than ensconcing the mistake of the past.

What on earth could I be talking about?


The problem is that if let makes a semantic copy of the optional payload. In some cases, the ARC optimizer can eliminate the additional reference counts etc, but it would be far more efficient to sugar the operation of "rebinding a name as a borrow of the optional payload" since this will be more efficient in general, and will work correctly with move-only payloads.

This is also important because the entire concept of if let x = x is about simple identifier expressions, not unwrapping arbitrary expressions like x.y.z.

If this is the better model that we want people to steer people towards, then I think it would be very destructive to sugar the copy syntax right before introducing the "right" thing. It would miss the opportunity to steer future swift code, and lead people to writing less efficient code in practice.


I think it is important to determine the right semantics (e.g. answering whether a borrow-model would be better or not) before tackling syntax. If a borrow model is better, then making the syntax be if let x { to form a borrow would clearly be wrong. I also don't see how something like if ref x { would be better than if unwrap x { etc.

That said, my meta point is that we should be starting from the semantic design and working towards syntax.

-Chris

26 Likes

I agree entirely: I almost never shadow an optional to unwrap it, as specifically using if let is mainly about attaching identifiers to properties that have a value.

if let x = y means “let the value of x be the value of y”, not “take y’s value whenever I say x”.

I recognize that my opinions about new shorthand seem to be in the minority in this thread, and I don’t mean to imply they are anything more than my opinions. That being said, I don’t even use if let that much, especially in idiomatic code.

Most things I want to do aren’t well-suited to the construct:

  • If I want to perform an operation on the value and work with the result, Optional.map is much more expressive.
  • If I want to have branching code paths based on the value, switch allows me to do that much more cleanly.
  • If I’m using method composition (which is almost always) and don’t need an else block, I have options like the postfix ? or Sequence.compactMap(_:).
  • If I want to proceed with an alternative value in the event one isn’t there, the infix ?? is usually much more concise.

As an example of a if let pattern I do use relatively often, I sometimes want to check a cache with if let, then return the cached value if it is there.
https://github.com/Saklad5/HTTP-Response-Date/blob/29db169d66a85f56e62f49a31687df7a20287c71/Sources/HTTPResponseDate/HTTPURLResponse%2BDateHeader.swift#L20-L29
It’s quite important that the value has a name distinguishing it from the source, so shadowing wouldn’t be useful. In fact, I’d see more value in some form of inverted guard that puts the optional binding in the else block, as this is really a form of early exit.

A lot of people have stated that if let x = x is an extremely common pattern in Swift code. Could someone give an example of when and why?

3 Likes

Given that it's possibly the most common construct in Swift (aside from basic syntax), you should be able find tens of thousands of examples online. But somehow I doubt you'll find them convincing.

  • If I want to perform an operation on the value and work with the result, Optional.map is much more expressive.

While a matter of opinion, my experience with less experienced Swift devs tells me this isn't the case at all. It's far easier for most Swift developers to grasp the if let syntax than it is to apply map to an optional. Perhaps this is a matter of how they learned to program, and perhaps they'd favor the functional syntax more directly if they had a functional background, but it's certainly not a common approach. Just because this is your preference doesn't make it the superior approach.

  • If I want to have branching code paths based on the value, switch allows me to do that much more cleanly.

Again, another opinion. I might even agree, if there was a captured value on the else side, like Result, but there isn't. So even if you like switches more for optionals, I don't see how you can call them "much" more clean.

  • If I’m using method composition (which is almost always) and don’t need an else block, I have options like the postfix ? or Sequence.compactMap(_:) .

Again, you should stop generalizing your preferred expression onto the entirety of Swift's user base. Many developers, especially less experienced ones, prefer straight-line, procedural code over chained composition. There are a few reasons for this, including the ability to easily introspect intermediate values and the obtuse nature of the functional APIs in general.

  • If I want to proceed with an alternative value in the event one isn’t there, the infix ?? is usually much more concise.

Use of ?? isn't a general solution to this problem, so I'm not sure how it applies here.

Additionally, you should remember that this proposal also applies to guard let and other unwrap constructs, which have separate use cases, so we're talking about a more general sugaring, and it should be considered as such.

6 Likes

I literally prefaced the entire post with “These are my opinions, and they seem to be in the minority in this thread.” You didn’t have to repeat that for each point I made thereafter.

At any rate, I was expanding on Chris’s point that if let is often not the ideal tool for the job, regardless of commonality.

The title of the proposal would imply (admittedly inaccurately) otherwise, and it certainly doesn’t change any of what I just said.

Just because something has a common use does not mean that common use is the ideal way to use it, that there isn’t a better tool for said use, or that the use should even exist. Just look at the perpetual controversy over force-unwrapping and the way newcomers to Swift misuse it.

With that in mind, I’m asking for a specific example (it doesn’t matter if there’s many, just pick one) of code that would benefit from the proposed shorthand. If that code would benefit from taking a different approach, it would be actively harmful to make the inferior approach easier.

I can think of a few examples with guard let, but this proposal isn’t just about guard let (and I don’t think you’d actually need to type it out that often anyway).

Most of the if let shadowing I find when I go looking seems like it could be written more clearly with other constructs:

init(value: Success, error: Failure?) {
  self = error.map(Self.failure) ?? .success(value)
}

As you noted, these are my preferences. However, I don’t think that makes them unworthy of discussion, especially since Chris pointed out that if let is not necessarily very efficient in terms of ARC traffic in the first place.

The intermediate values are, I’d argue, the root of the entire issue. We should not dismiss them as simply being a preference.

2 Likes

It's so trivial to come up with examples of using this feature I have a hard time believing this request is in good faith, but I'll indulge you once.

This is very common when building out views from data models in SwiftUI. Say you want a view for a user's info. You provide the data and then build a view.

struct UserView: View {
  let name: String
  let phoneNumber: String?

 var body: some View {
    VStack {
      Text(user.name)

      if let phoneNumber = phoneNumber {
        Text(phoneNumber)
      }
    }
  }
}

Of course, this proposal would slightly simplify that down to

if let phoneNumber {
  Text(phoneNumber)
}

If these are your preferences, fine, but you also seem to be making an argument for their superiority, or at least saying they should impact this proposal. Mere preferences don't really need such discussion, as we don't base language decisions around them. If you have a real argument to make, make it, in which case you should expect people to offer counter arguments. You can't have it both ways.

Personally, I find your version of the Result init unnecessarily succinct, making it less readable, both for me when I come back to the code in six months, and for other users. There's often a way to express code more succinctly, that doesn't make those ways inherently better.

Like I said, either you're stating your preference, in which case they're freely dismissible, or you're making an actual argument, in which case people will respond. You can't have it both ways. Chris was making an actual argument, that Swift should reevaluate how it expresses unwrapping pattern matching in general, rather than sugaring the existing syntax. He wasn't just stating a preference. Personally, I don't have much to say about that argument, but I really don't see how your posts are relevant to his points.

4 Likes

Isn’t that a different construct entirely, as part of result builders? Would it be covered by this proposal? That’s definitely a reasonable example, if so, as you can’t use other approaches there.

I don't think so, the expression is just translated into something the ResultBuilder can use by the compiler. In any case, that's not really relevant there since I would use the same expression to build a UIKit view.

Actually, you can, and it's what we had to do before ResultBuilders supported if let:

phoneNumber.map { Text($0) } // Probably can't use .map(Text.init) due to the various overloads.

However, this is decidedly less readable (to me), which is why support for if let was added to ResultBuilders.

4 Likes

Huh, never knew that. I can definitely see the issue there, as having it on the same line is quite harmful given how result builders are supposed to be read.

Breaking it onto multiple lines would make it look more like a chained modifier than a conditional. That’s understandable, since chained modifiers are actually implemented like map behind the scenes.

This is a nice example, thanks!

Yeah, this proposal automatically includes support for results builders, since it is transformed to the existing if let foo = foo syntax at parse time. I just double checked to be sure, and this is supported in the current implementation:

let name = "..."
let phoneNumber: String? = "..."

@ArrayBuilder<String>
var someResultBuilder: [String] {
  name
  
  if let phoneNumber {
    phoneNumber
  }
}
6 Likes

Wait, are you telling me this is how that would be written without the shorthand?

if let phoneNumber = phoneNumber {
  phoneNumber
}

Something about that feels extremely off, somehow. Like an English sentence composed solely of the word “buffalo”.

2 Likes

If the builder takes values directly, yes. That's the shorthand builders enable. Technically you could have the builder support optional values directly, which is how the map version worked in ViewBuilders, but it's not a requirement.

3 Likes

Certainly.

if ref x seems better to me for the same reason if let x is i.e. it's just a small sugaring of existing (or to-be) syntax, rather than introducing a whole new keyword specifically just for this "rebind" use case. But it'll also be even less flexible and clear if/once ref is a thing:

You also want to distinguish mutable vs shared borrows. So we may end up with inout x = a[i] and ref x = a[i] respectively. If that's the case and we end up with this 2x2 matrix (let, var, ref, inout) then if let x being sugar for if let x = x with the RHS dropped can be applied to all four kinds of binding i.e. if inout x = x becomes if inout x. Bear in mind that unwrapping with inout is much more compelling than var because it opens up the ability to mutate values in-place.

To introduce a new optional/enum-specific keyword like unwrap instead would now be less clear: which of the 4 is it doing? Presumably ref because that's "usually better". But ref and let aren't indistinguishable, because of exclusivity, which matters because...

Well, maybe. It's reasonable that this feature might be extended to have if let x.y.z be sugar for if let z = x.y.z. Presumably this is how if unwrap x.y.z could behave too.

More importantly, should if let x = self.x be sugared as if let x (in a way that matches { [x] in ... }, as @Jumhyn points out above)? I think so. It'd be pretty surprising/annoying to people if not.

So now you end up with:

// unwrap self.x
if unwrap x {
  // use x
  self.x = nil // nope, exclusivity violation
  // or indeed...
  self.mutate() // also nope
}

Not the end of the world, they can restructure their code to use if let once they understand the error – which unwrap makes less clear than if this were an explicit if ref x. This seems like a notable downside versus the more explicit, flexible, yet equally succinct option of sugaring all four options.

7 Likes