A natural extension of this new syntax could be to support shorthand for optional casting. For example:
if let foo as? Bar { ... }
could be equivalent to:
if let foo = foo as? Bar { ... }
This is not included in this proposal, but is a reasonable feature that could be added in the future.
This wasn't included in SE-0345 because it makes the parsing and formal grammar a bit more complicated. I suppose at the time SE-0345 was still somewhat controversial, so there were bigger things to worry about.
In retrospect it seems perhaps a bit confusing to not support this, since it's visually very similar to the supported if let foo = foo case, and the = foo here isn't required for any semantic reason. Are there any other cases like this, that seem superficially related to if let foo = foo but which don't support the shorthand from SE-0345?
Should we still have the question mark after as in this shorthand? There isn't really anything related to optionals happening in this statement. The equivalent case condition case let value as T = value doesn't use the ? symbol at all.
Also, for if let foo = foo and if let foo I think of it as just eliding / omitting the redundant = foo, rather than using a different concept or syntax entirely.
If we apply the same to if let foo = foo as? T, it gives us if let foo as? T. Keeping this model would seem preferable for consistency and predictability.
I understand the appeal, but I don't think reusing the name is a good idea. By casting, we make something more specific, which deserves a more specific name:
if let cat = animal as? Cat {
// ....
} else if let dog = animal as? Dog {
// ....
} else {
// ....
}
When you're not casting, but actually dealing with an Optional, the progression of sugar is clear:
let value: Optional = 0
if case .some(let value) = value { }
if case let value? = value { }
if let value = value { }
if let value { }
But otherwise, there are two forms to shorten, depending on if you're adding optionality or not:
let value: Any = 0
if case .some(let value) = value as? Int { }
if case let value? = value as? Int { }
if let value = value as? Int { }
if case let value as Int = value { }
The latter form is not what was addressed in Swift 5.7. So while only what's already been proposed is necessary for consistency—and readability— I propose that shortened case forms should also compile:
let value: Any = 0
if let value as? Int { }
if case let value as Int { }
let value: Optional = 0
if case let value? { }
This is all with the caveat that the existing spelling has always been broken.
We should have gotten the proposed if let value? (leading to if let value? as? Int for this thread) instead, but we didn't, because what should have been in the language from the start,
I should have put in my original message but there's a functional difference with your observation of
if previousMessage != message
and my comparison
if let previousMessage, previousMessage != message
where yours won't check for non-nil values whereas mine is ensuring the value is non-nil as well as a different some-value.
My original code should have been expanded to include the nil check, eg:
var previousMessage: String? = "foo"
var message = "bar"
if previousMessage != nil, previousMessage != message {
print("message changed")
}
Perhaps a bad/overly simple example on my part in my original code.
Another example is you could imagine this being even more useful in a guard in real code where it's more typical to do if let combined with some comparison before bailing out early, especially in more complex/real world code which might have many guards with more complex comparisons.
var previousMessage: String? = "foo"
var message = "bar"
guard let previousMessage != message else {
return
}
print("message changed")
This can be quite confusing if second value also optional:
var previousMessage: String? = "foo"
var message: String? = "bar"
if let previousMessage != let message { // what is happening here?
print("message changed")
}
var previousMessage: String? = "foo"
var message: String? = "bar"
if let previousMessage != let message { // what is happening here?
print("message changed")
}
I wouldn't say this code was confusing, maybe just unusual at first?
The only possible confusion I would see is if you drop the second let and then it's not quite clear if the value on the RHS is unwrapped or if it's still the original value, eg:
var previousMessage: String? = "foo"
var message: String? = "bar"
if let previousMessage != message { // is `message` String? or String
print("message changed")
}
But my intuition looking at that is without the let it's simply not unwrapped and is actually still a String?
I mean different executing paths will be in the following cases:
var previousMessage: String? = "foo"
var message: String? = "bar"
if let previousMessage != message {
// previousMessage is compared to message no matter message contains a value or nil
} else {
// executed when:
// - previousMessage != message's wrapped value
// - previousMessage != message because message is nil
}
if let previousMessage != let message {
// previousMessage is compared to message if message is only a `case .some(wrapped)`
} else {
// executed when:
// - previousMessage != message's wrapped value
// - previousMessage != message because message is nil
}
While the else body in both examples is executed by the same conditions, the equality operation in if-expression is executed differently, which may be unexpected and an can accidentally happen due to inattention. These two pieces of code look very similar but do different things.
I would prefer to write this code explicitly making my intent clear:
if let previousMessage, previousMessage != message {
} else {
}
if let message, previousMessage != message {
} else {
}
if let previousMessage, let message, previousMessage != message {
} else {
}
if previousMessage != message {
} else {
}
Separation of if-let unwrapping and further usage of unwrapped values seems more understandable and less error prone to me.