Yes I agree, but for that we have map already no? (Yes, semantically is not the same)
To be clear I’m bot in favour of changing anything ^^ I’m just pointing it out :)
The if let x
proposal comes up every few months and always sparks a lot of discussion.
To my mind, this seems like a pretty good indication that the lack of this syntax is more surprising than the syntax itself would be. It's an obvious and natural extension of the existing syntax, so every new Swift developer tries to reach for it and then discovers it doesn't exist.
There's nothing wrong with
if let foo = foo {...}
but there's something really unpleasant about
if let ajaxHTTPRequestResult = ajaxHTTPRequestResult {...}
It creates a dazzling effect that forces you to glance again at the code just to verify that it's only an unwrapping. What's more, the verbosity pushes you in the direction of writing something like
if let result = ajaxHTTPRequestResult {...}
which I would argue is an antipattern. It's fine if the body is a few lines long, but once you get to the point at which the immediate context is not apparent, you now have two different names for the same thing. (Yes, yes, they are not literally "the same thing," but the expressive intent is indeed "the same thing, but now unwrapped.") The common Swift style embraces shadowing when it's clear and unambiguous, and I'd like that convention to continue.
I'm surprised by the number of suggestions in this thread that propose new keywords or new syntax to address this issue. These proposals seem poorly motivated, and I suspect that they're largely a consequence of the reflexive aversion to syntactic sugar that's broadly evident in this forum. Nobody's likely to get very far if they suggest, "Hey, don't we need some syntactic sugar for X?"
But in this case, we really do need just a bit of sugar. if let foo
should be, identically and interchangeably, a shorthand spelling for if let foo = foo
, with all that that entails.
All of @jrose's edge case traps will still be available for the unwary to fall into. But they're already lying in wait with the current syntax. If the rule is that if let foo
is precisely equivalent to if let foo = foo
, no one will have the slightest difficulty making the implicit translation, any more than they have trouble knowing that "can't" means "can not."
if let x
is not "really" shadowing or "really" unwrapping or "really" some tangential new thing that never existed in Swift before. It doesn't need any confusing, heavyweight hoo-hah backing it up to make it palatable.
A problem with this though is how to deal with the fact foo
represents two things, the optional foo
and non-optional foo
. If you click on it which other symbols does it highlight? The same goes for finding every instance of a symbol, and in particular refactoring using Xcode's tool.
if let foo = foo {}
doesn't have any of these problems. It's clear the two symbols are different. If you want to emphasise it by giving them different names, perhaps with a convention for optional types, then you can. But I find this unnecessary in Swift, where type safety means types are normally clear from the code around them, including their position in patterns like this.
It is shadowing and unwrapping, though. Not that I have a problem with that. I think that's the point of it.
The same ones that highlight if you use if let foo = foo
. It is shadowing, after all.
I think you misunderstand my point. A larger example perhaps illustrates it:
init (foo: Foo?) {
// ...
if let foo = foo {
doSetup(with: foo)
} else {
defaultSetup()
}
}
If in Xcode I click on the first foo
in if let foo = foo
it highlights occurrences of the optional foo
. If I click on the second foo
in that line I see the non-optional foo
higlighted. This is a trivial example, in real code the optional and non-optional foo
might occur many times.
Suppose I want to refactor the optional foo
, change it to optionalFoo
, I can do that in Xcode now. But if instead my code contains if let foo
what happens? Does it also change the name of the non-optional foo
? Does it change the line to e.g. if let foo = optionalFoo
? Or does it simply refuse to refactor the code?
OK, so the question is if you select the actual foo
there in if let foo
, which one does it count as? That is tricky. I think I'd lean towards treating it as the unwrapped version.
Yes, there isn't a necessarily obvious answer. I think I'd vote for either ask the user, or refuse.
Out of curiosity, I experimented with the relevant IDE behavior for the analogous situation with captured variables, using this small test case:
let test = 0 // 1
let closure = { [test] in // 2
print(test) // 3
}
For highlighting:
- Clicking on (1) highlights (1) and (2)
- Clicking on (2) highlights (2) and (3), unless (1) and (2) were already highlighted (in which case there is no change).
- Clicking on (3) highlights (2) and (3)
For the "Rename" tool:
- The "Rename" operation on any of (1), (2), or (3) renames all occurrences, but it's broken (SR-14661). For instance, "Rename" on (2) followed by typing "test2" produces the following output:
let ttttttettteesttttettteesesttttttettteesttttettteesestest2 = 0
let closure = { [ttttttettteesttttettteesesttttttettteesttttettteesestest2ttttttettteesttttettteesesttttttettteesttttettteesestest2ttttttettteesttttettteesesttttttettteesttttettteesestest2] in
print(ttttttettteesttttettteesesttttttettteesttttettteesestest2)
}
For "Edit All in Scope":
- Editing (1) changes (1) and (2)
- Editing (2) changes (2) and (3)
- Editing (3) changes (2) and (3)
All of these "work" in that they produce reasonable output (compared to the "Rename" functionality), but the resulting output doesn't compile since if (1) isn't changed, the new name for (2) doesn't refer to anything to initialize from, and if (3) isn't changed then it no longer refers to anything either (unless, of course, there's an additional shadowed name...).
Wait, what? I thought I understood what this expression was, but this is the opposite of what I expected:
if let innerScopeNewlyDefined = outerScopeAlreadyDefined { … }
It is actually the opposite? Is the symbol on the right hand side actually the new symbol?
No, the inner symbol is definitely the new one. Is xcode really highlighting them backwards?
Oh yes - I didn't mean to imply otherwise. What I object to is the idea that if let foo
is such a specialized variant of if let foo = foo
that it needs a special keyword or marker (such as unwrap
or shadow
) to focus attention disproportionately on one particular aspect of the construct.
@JohnBlackburne, I agree with your point that a shortened form creates a potential UI problem for code editors. However, that's going to be true for any of the abbreviated forms proposed in this thread, since eliminating the duplicate name is the feature they all share in common.
The point of Xcode or another IDE selecting multiple instances of a name is either to help visualize where the name is used in the code or to facilitate renaming. Since if let bar
demonstrates a clear intent to use the same name for both variables, I would imagine you'd always want to see or rename both variables in tandem. So the UI should select all instances of the name, including those both inside and outside the if
.
Of course, this presentation wouldn't be strictly correct in terms of language structure. But I suspect that it would be close enough to what users want and expect that it would not even be perceived as a special case.
Ah, well spotted, thanks.
I still think that if if let foo = foo
is so common that it needs extra sugar, then we should just use if let foo?
since that's an obvious sugaring of if case let foo? = foo
.
The only ambiguity (that needs to be resolved) is when you click the foo
in if let foo
. If you click any other occurrence, it will continue to behave like today.
This is very much an edge case. We shouldn't optimise for when you want to change from using the same name to using different names. The important thing is to handle the same-name case, and possibly the different-name case on their own (although many seem to think it's an anti-pattern).
This is my preferred approach. I suggested it here: https://twitter.com/erithacus_/status/1382440106758447108?s=21
I don't think this is really much more weird than changing a type by adding a ! at the end to force unwrap. From a readability point of view, it would be great.
If it isn't nill then do this. It reads very clearly. Unlike if let which makes little sense from a natural English point of view.
Big +1 for the guard/if let x {
syntax. It feels like the most natural solution.
guard let y else { return }
...
if let x {
...
}
Seems that now Xcode autocompletes if let x = x.
It doesn’t fix the concerns of many here but is an improvement:)
I think the let
keyword in this case tends to cause confusion… It reads like the optional variable/constant (in your example, fooViewController
) gets defined again…
It does get redefined. Inside the if fooViewController
is a non-optional view controller. It's just like if let fooViewController = fooViewController
or if case let fooViewController? = fooViewController
.
This is fantastic, I love the idea. It will make Swift code so much more readable, maintainable, and with less boilerplate. Hoping to see this get added to the next version of Swift.
While, as a verbose ObjC/HyperTalk fan, I regret Swift's terseness, and agree that if let foo = foo
means nothing to normal humans, I think fixing the Rename refactor would definitely be a worthwhile endeavour whatever way this pitch goes.
It can't be that hard to detect that the optional and its shadow have the same name before refactor and then change them both.