Hello all. I used to be a proponent of team if let x { … } till I read a well-considered proposal draft regarding unwrap that @Erica_Sadunonce linked me to. I think the Unwrappable protocol introduced in that proposal can go beyond the specific use case being discussed in this here thread.
On a tangential note, it’s heartening to see Chris being open to reconsidering a decision made years ago, now with the benefit of almost six years of the Swift community’s collective experience.
I have some encountered feelings with this. I see all the positive words on the thread so is probably just me being on the wrong side but is not that clear to me that changing it is all positive. So I will just leave my 2c here.
I'm specially around this feeling of hiding even more the concept. of optionals. I think they are a very basic building block of the language and shouldn't be hidden more. In my experience, learning them is more of a struggle for people with baggage from other languages than newcomers, and really hiding them even more would just make them even harder to understand. if let = is already sugar, but is one that shows you what is going on: you are combining a check (if) and a binding (let =), which helps to understand that there is no magic here. I think that getting rid of that will just make optionals more confusing.
Furthermore, yes this is a very common pattern, but there is also tons of other code that won't benefit from this. And then you will end up with multiple ways of really doing the same thing, which is not bad per se but I feel that in this case it would be detrimental.
Using typescript's approach of making things non-nil contextually (after a x !=nil) is the change that looks cleaner to me since it doesn't add any special syntax, it just makes the compiler smarter and is still easy to understand. But it also feels kind of weird in Swift that types change without being explicit. And personally I'm not a fan of seeing nil in the code, it just confuses things even more for people that comes from languages with null.
Ultimately this is something I haven't found to be a problem after all the Swift code I've written since day 1. Is one of these things that I feel like if the tools were a bit better we wouldn't even be complaining.
My impression was that Unwrappable was much more specific to Optionals, e.g. most other monoidal types (Promises, Collections, State, IO, Either, and so-on) would either be completely unable to support Unwrappable or at least having ambiguities.
E.g. a result might have if unwrap result.success to clarify against if unwrap result.error, but since the point is to shadow the variable result neither of these work. You might instead need if try unwrap result to represent both flows.
Likewise with a task handle/promise, it would be something like if async unwrap result to account for the possibility that the task has not completed yet.
Independently, Optional has the benefit of automatic conversion from type T to optional T? when needed. This means even when shadowing a value with an if let statement, you can still use use that value in contexts that expect an optional version. Mapping the success of a result or the eventual value of a future would likely not have that compiler support for automatic conversion, making them less useful in this sort of context.
Shorthand syntactic sugar for 'variable shadowing'
rather than if let, guard let or case let etc?
Because I mean, type casting if let v = v as? OtherType { ... } is not included in this discussion, is it?
Also as mentioned by @ensan-hcl , if a value is chained, it would not work.
Keyword unwrap sounds natural to me as it is already used in stdlib:
That being the case, how about if case let x? {} as a shorthand for if case let x? = x {}? It won’t be immediately obvious to passing Typescript developers, but it’s reasonably terse and expresses the major points:
Here's a half-baked idea. We usually talk about moving complexity out of the compiler and making the standard library more capable. Since Optional wrapping is causing the confusion here (which happens to be a standard library type), instead of introducing new keywords we can define new methods on Optional that the compiler can use to make decisions and do variable assignments.
if x.unwrap(to: let y) {
print(y) // not optional
}
// y is inaccessible here, as it was defined inside the scope of the if statement
I don't love the current if let syntax, but this feels like it is fixing a very specific special case, where I'd rather see a more general addition to the language which would let us fix this ourselves
for example; If a function was allowed to access it's caller, then we could write
func ifPresent(_ block: (Any)->Void) {
if let nonNil = #caller {
block(nonNil)
}
}
which could be used as
foo?.ifPresent() {
$0.rect = .zero
}
for bonus points, we could have a way of defining default variable names when calling functions. Throw in access to the caller's string name, and you have
func ifPresent(_ block: (Any)->Void) {
if let nonNil = #caller {
block(nonNil @VariableDefaultName(#caller_string_name))
}
}
which could be used as
foo?.ifPresent() {
foo.rect = .zero
}
or
foo?.ifPresent() {
preferredName in
preferredName.rect = .zero
}
there are a bunch of other places where @VariableDefaultName would be great.
for example
networkRequest.run {
/// @VariableDefaultName has already implicitly written
/// request, data, error in
if error.isNil {
data.process()
}
}
note: we wouldn't need access to #caller if there was a way to write
extension Any {
func someFunc() {
//I can now access self, rather than needing #caller
}
}
The current if let syntax creates a new variable. It is not necessary to have access to the caller, because the bound variable in an if let is a copy anyway.
You can write something like
extension Optional {
func ifPresent(_ block: (Wrapped) -> Void) {
if let wrapped = self {
block(wrapped)
}
}
}
and all you're missing is the ability to make the block parameter read-write (var).
From this, I'd also like to say that new syntax sugar should imply this limitation. If we chose have for example, then the next code would seem natural. But it would not work.
if have data.value { /* ... */ }
Likewise, if exist data.value, if unwrap data.value, if data.value?, if nonnil data.value all seem natural but would not work.
New syntax should contain let. The next syntax seems weird, because in Swift no expression after let is a property. It is great that impossible thing seems weird, isn't it?
// it is impossible to declare property or function
let data.value = 42
let function() = 42
// it is also impossible!
if let data.value { /* ... */ }
if let function() { /* ... */ }
It is also good because it can declare mutability. To use let and var, possible and readable ways would be if let x {}, if let? x {}, if let x? {}. I think it's okay to insert keyword before let/var like if unwrap let x {}, though it makes the code longer. As @benrimmington mentioned, if let '' = x {} is also good from this aspect.
@VariableDefaultName() would also be great in so many other situations - e.g. having to explicitly provide variable names on any AFNetworking call just seems painful.
I see. I missed the nuance. It's an interesting idea, but I'm not sure how much it generalizes, to the point that it would be worthwhile sugar.
I think what you're looking for is a mechanism to signal that the object on which a method is invoked should be available as the sole parameter of the method.
The problem is that this breaks down:
Optional(3).ifPresent {
// What is the parameter name?
}
yes - in this case #caller_string_name would be nil, and you'd have to fall back to the old method of explicitly providing a variable name or using $0
(you'd still write your extension the same way - you just wouldn't have access to an auto-named variable when you used it)
I'm not only interested in this situation though - I think @VariableDefaultName() could tidy up the API in a gazillion other areas (e.g. the networking library example above)
Swift has enough magic variables, but at least the names are defined by the language (e.g. newValue, error in catch blocks, etc). I can't see this idea getting off the ground. The caller might not even have visibility into the callee's source to find out which magic names exist.
I don't understand why everyone is trying to invent a different spelling here when if let x is seemingly unambiguous and so obvious to Swift developers that it has been repeatedly proposed/discussed in that form over the 6+ years I've been reading the Swift forums/mailing lists. If it's not going to be if let x then I'm going to struggle to support it.
That it doesn't naturally work to unwrap other.thing is a feature to me, because you should probably think about giving other.thing a different name in this scope (e.g. otherThing, specificThing), as opposed to if let x where x should already be a name that makes contextual sense.
that's a fair objection - though the compiler certainly would know those names and could show them in various ways (not least alt+click on the function which currently shows the definition)