I was reading the discussion on Let’s fix if let
syntax. It's a good discussion, but it overlooks how weird Swift is to encourage us to create variables that replace existing variables but with a different type (optional type replaced with non-optional type). let x = x
is a weird pattern, but we are all just accustomed to it.
Meanwhile, the proposal to just use if x != nil { }
is not getting enough attention. There was an objection saying "I find it really weird that merely performing a comparison would change the type of a variable from optional to non-optional." But what if we weren't changing the type of a variable? What if it is the same variable - the Swift compiler just infers what we want to do with it based on prior knowledge.
There is this other similar objection, saying that setting value to nil in this code would cause an error:
var value: Int? = 42
if value != nil {
value = nil // Error because value is now non-optional
}
It was proposed that this would cause an error because value
became a non-optional inside the if block, but then we try to set it to nil.
But I don't think this should be an error, because value
should be the same variable, not a new one that replaces the old one with a different type.
So, really it should be like this:
var value: Int? = 42
if value != nil {
let x: Int = value // This is fine - the compiler should infer that this optional is of case .some and stick the int in here.
value = nil // This is fine too - because `value` is still Int?
}
print(value) // Prints nil - because we changed `value` in the if block.
And modifying value
later can also lead the compiler to lose its ability to infer the case in subsequent code:
var value: Int? = 42
if value != nil {
let x: Int = value // This is fine
value = nil // This is fine too
let y: Int = value // This is not fine, because value has been changed to something that no longer guarantees it is non-nil - Error: Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
}
Enum Case Inferencing:
This behavior can actually be built into all enums, not just Optional.
For example, say I have an enum:
enum FooBar {
case foo(String)
case bar(Int)
}
Now I use a FooBar:
func useFooBar(fooBar: FooBar) {
let x: FooBar = fooBar
let s: String = fooBar // Cannot convert value of type 'FooBar' to specified type 'String'
}
I can't convert it to a String obviously - it is a FooBar. But what if the compiler knows it is case .foo?
func useFooBar(fooBar: FooBar) {
guard case .foo = fooBar else { return } // Compiler now knows this is case .foo
let x: FooBar = fooBar
let s: String = fooBar // This is fine, because the compiler knows this is case .foo which can be unwrapped as a String
}
We could also use this with multiple parameters, and the compiler can infer it as the tuple:
enum FooBarWithParams {
case foo(x: Int, y: Int)
case bar(Int)
}
func useFooBar(fooBar: FooBarWithParams) {
guard case .foo = fooBar else { return }
let v1: FooBarWithParams = fooBar
let v2: Int = fooBar.x // Accessing parameter by name - compiler knows this is case .foo which can be unwrapped as a tuple of (x: Int, y: Int).
let v3: Int = fooBar.0
let (x, y) = fooBar
}
Plus, maybe with this we can start to leave behind ugly case
syntax.
Anyway, inferencing is cool - we all love how the Swift compiler infers so much when it comes to types. Type inferencing helped Swift leave behind a lot of cruft from the C world, and we are all thankful for it. Enum case inferencing seems like a similar step - the compiler often knows exactly what case we are dealing with (and so do we) - it should use that information for our convenience in an obvious and intuitive way.