Just to add to this, it isn't clear to me that we need a solution that is generalizable to all swift assignments.
Improving if let and guard let could be worthwhile because that pattern is very common and it's quite verbose to repeat the variable name. Shadowing is mostly a side effect of checking for nil here. It's fine IMO if other kinds of shadowing are kept as-is.
I'm not really getting why if let x = x promotes bad variable names more than let x = MyX(). Bad variable names are bad variable names. if let x ( one of the proposed spellings) will still have a bad variable name if the original x that's being shadowed is a bad variable name.
Bad variable names is a problem solved by a linter or code review.
There is a certain redundancy in the if let x = x syntax, of course, and I suspect that's the main reason people would like to improve this.
+1 on the unwrap keyword here. It fits into the readability-focused reasoning around the change, and it's clear exactly what is happening.
I agree with the overall thinking of this change, I've seen many odd shortened versions of variable names used, including meaningless names such as 'x'. Whilst this should be avoided by better naming, a change here would eliminate any potential for this issue.
If you read my original post I was not specifically suggesting overloading *, it was just a placeholder for something better.
I think a solution that would be conceptually identical and reusable for reducing “var of same name assignment” everywhere would be a prudent approach unless there are good reasons to say “we’d never support a mechanism to do this outside of if let ever”.
I type so much initialiser assignment boilerplate every day it is truly tedious, and added to the same-name “if let xxxxxxxxxx = xxxxxxxxxx” we are also typing all day I have to intuit this would be incredibly popular — and its use would not be mandatory so if you didn’t like it that would be fine.
I don’t want to see some odd new if let syntax that has only one purpose, when to language users the concept of “assign this the existing value with the same name” is utterly intuitive and applicable in many more scenarios.
@Marc_Palmer (I forgot to click the reply button )
Thank you for clarifying. I agree with the treat of x.y = * and the problem of renaming.
I'm a bit concerned about cases where unluckly it didn't cause compile error (when we have another variable whose name is equal to new name), but it would be trivial.
Currently, while you write if let x = x {} in Xcode, for the first x, autocomplete does not work as written in the initial post.
In the same way, in if let x = * {}, autocomplete for the first x would not work still. I don't think it is a serious problem, but I concern for some 'lazy' people it would be still annoying. I agree that = * can reduce needs for autocomplete on the right side.
How about labeled function? If range(from: *, to: *) means range(from: from, to: to), I think it potentially discourages people from defining function that uses preposition as labels like range(from num1: Int, to num2: Int) -> Range<Int>, because no one wants to have variable named from or to even as local variables. I don't know how much it matters, though.
My pushback to introducing a new sigil to represent "redeclare this variable" isn't affected by the choice of sigil so * being a placeholder is not important.
Such syntax being out of keeping with the general direction of Swift aside, I don't think "redeclare a shadow copy" is a good pattern to encourage, so yes, I think keeping a focused solution of if let x { or similar would be a better solution than attempts to generalize.
For example, another common case of "redeclaring" a variable of the same name is something like this:
func addSamples(_ newSamples: [Sample]) {
var newSamples = newSamples
// perform mutation on samples in various ways
self.samples += newSamples
}
Generally speaking, you probably want to name the mutable newSamples to something different – something representing the updates you're applying before assigning them. Actual shadowing could cause significant confusion between these two now very different variables. But var newValues = * shorthand would encourage this situation.
if let x { does not have this problem. x is fundamentally the same x, except unwrapped. The name sharing is perfectly legit.
Now this does lead to the question of whether if var x { should be allowed (after all, you can today write if var x = x). My feeling is maybe not? There is already significant potential for confusion with if var x = x because you are not mutating the outer x, but at a glance you might think you are. This is similar to the confusing and since-removed f(var x: Int) syntax as others have noted. Then again, some day hopefully we will get the ability to do in-place mutation i.e. if var &x { x += 1 /* mutates value inside outer x */ } at which point the same-name would once again be appropriate.
Your other compelling use case is in initializers. I definitely agree initializers are an ergonomic pain point. I suspect that this needs different solutions though – some kind of targeted member initialization syntax that lets users more flexibly declare which properties a user should be able to supply to an init and automatically applies them. It's possible an idea like = * could end up being the right solution here, though I am skeptical. But initializer ergonomics is a big topic that deserves its own thread instead of a tangent discussion here. And also one that is unlikely to be solved in the very near future. It would be a shame to hold up what seems like a much more of a targeted and achievable solution for if let specifically.
Just one data point from someone who started pick up swift recently. The current syntax is a bit jarring to the eyes as it first looks like a self assignment. Not sure what would be the best syntax, but definitely agree it would be nice to improve this (the simple if let x { seems ok). So +1 to the pitch sentiment.
There seem to be two concerns that prompted this discussion:
no autocomplete for the LHS of this pattern, which makes long variable names annoying
refactoring does not recognize the shadowed name as being the same as the original
Both of these are tool problems, specific to Xcode (re: item 1, other editors do basic string-based completion just fine). And I think it's important to note that neither of them is automatically fixed by new syntax, because the tool has to be updated anyways to support whatever the new syntax is.
I personally don't see a reason to implement a new syntax.
Using Kotlin a lot, they have 5 scope functions, let is one of them:
extension String /* Any */ {
func `let`<R>(block: (String) -> R) -> R {
return block(self)
}
}
let s1: () = "s".let { s in
print(s) // s is String
}
let s2: ()? = (nil as String?)?.let { s in
print(s) // s is String
}
Swift "just" needs to implement these functions too.
Kotlin has an also function too, which execute the block with the variable, but returns the variable.
extension String /* Any */ {
func also(block: (String) -> Void) -> String {
block(self)
return self
}
}
let s3: String = "asdf".also { s in
print(s)
}
I think, if we would go in the direction of adding a new keyword to achieve this, I wonder if it wouldn't be more useful to have a higher level feature that allows to implement such a keyword in the language itself (similar to property wrappers).
if let myOptional = myOptional.unwrap() {
}
extension Optional {
func unwrap() -> Wrapped? {
return self
}
}
The neat thing would be you could use it for other use cases as well. For example verification:
if $verified userInput {
}
extension UserInput {
func $verified() -> VerifiedUserInput? {
// If `self` (UserInput) is valid we return .some(VerifiedUserInput) and
// the if body is executed and the variable is reassigned to `VerifiedUserInput `.
// Otherwise we return `nil` and the `else` branch is executed.
}
}
This could then also be extended to support parameters:
map is supposed to just unwrap the value and transform it, like in an array map. Just like we have a forEach to perform operations on each element in an array (please don't use map for this!) we should have a corresponding method for optionals:
foo.ifPresent { print("Foo is not nil, it's \($0)") }
The stated problem is that there is no support for optional binding in the autocomplete feature nor the refactoring feature of Swift’s tooling. As expressed previously by @QuinceyMorris and @Terje (and perhaps others… long thread), I think the immediate—if not long-term—solution should be tooling changes rather than language changes.
I agree with improving tooling support. Specifically with suggesting Optional variables first after an if let ... = et similia.
However, I don't personally think a refactoring involving shadowed variables should exist. In the event that the user explicitly wanted to differentiate the name of the variable from the name of the shadowed one, how would the refactoring proceed if the editor automatically and indistinguishably renames everything?
That would create a situation in which, if you happen to have named a variable with the same name of the variable you are shadowing, then you can't go back differentiating them.