8 ms·
They have different use cases. if x != nil is to do some work without needing x, because you'd still have to unwrap it if you used it. That's what if let y =
by mckn1ght 3y ago
They have different use cases.
if x != nil is to do some work without needing x, because you'd still have to unwrap it if you used it.
That's what if let y = x is for, it creates a safe lexical context to use the unwrapped value.
And then you have guard let y = x which is a stronger guarantee that no code further down in the context is executed if x is nil. This helps to write happy-path code with early returns, avoiding pyramids of doom and encouraging writing code where all the assumptions are listed up front with logic handling exceptional cases before finally arriving at the logic that works under all the assumptions.
Dart feels like a step backwards after seeing the benefits these provide.
- afavour 3y ago> They have different use cases. But there’s also no harm in combining them. I agree with the OP, TypeScript does what they describe Dart doing and I find it much simpler than the way Swift does it. It’s never confusing.
- mckn1ght 3y ago> But there’s also no harm in combining them Disagree. I’ve seen people write things like the following: if x != nil { x!.foo() } and then later the x != nil condition is changed to no longer check for nil, or the x!.foo() is moved out of that lexical scope elsewhere, or copypastad, and then the force unwrap crashed. I’ve seen similar yet more subtle bugs with soft unwraps instead of forced: x?.foo() Not possible if you use if let y = x . If you copypasta the inner scope with the different variable name then it won’t compile. (ofc these are trivial representative examples, in reality these were lexical scopes with many lines and more complicated code) It’s not just about how the code looks when you write it, it’s the paths that can be taken by future maintenance programmers. I‘ve complained about the large surface area of swift syntax, stdlib etc in the past, but I don’t personally find this specific thing confusing.
- refulgentis 3y agoFor removing the null check, that, of course, would cause a compiler error. For moving the force unwrapped out of the if, that's a problem with force unwrapping, not combining the syntaxes.
- saagarjha 3y agoChanging the type of something based on a conditional check is not great, because if I wanted a non-optional I would request it. Sometimes a nil check is literally just a nil check. What if I want to set the value back to nil if it is not-nil? Why should I have to wrap things back in an optional if I want to pass it along as such?
- mraleph 3y agoThe reason why languages promote variable types based on control flow is because developers en masse actually expect that to happen, e.g. facing the code like Dog? maybeDog; if (maybeDog != null) { maybeDog.bark(); } If compiler says "sorry, maybeDog might be null", developer (rightfully so) usually responds "but I have just checked and I know it is not, why do you bother me?". So languages chose to accommodate this. > What if I want to set the value back to nil if it is not-nil? You can. The type of the variable does not actually change. You could say that the information about more precise type is simply propagated to the uses which are guarded by the control flow. The following will compile just fine: Dog? maybeDog; if (maybeDog != null) { maybeDog.bark(); maybeDog = null; } > Why should I have to wrap things back in an optional if I want to pass it along as such? You don't, with a few exceptions. There is a subtype relationship between T and T?: T <: T?, so the following is just fine: void foo(Dog? maybeDog); Dog? maybeDog; if (maybeDog != null) { maybeDog.bark(); foo(maybeDog); // Dog can be used where Dog? is expected } You might need to account for it in places where type inference infers type which is too precise, e.g. Dog? maybeDog; if (maybeDog != null) { // This will be List<Dog> rather than List<Dog?>. final listOfDogs = [maybeDog]; } Though I don't think it is that bad of a problem in practice.
- saagarjha 3y agoSee, I don't think languages should accommodate this, because I see it as an ugly solution. It's nice that it works in a few cases but then it very quickly breaks down: a developer finds that their null check is enough to strip an optional, but a test against zero doesn't convert their signed integer to unsigned. Checking that a collection has elements in it doesn't magically turn it into a "NonEmptyCollection" with guaranteed first and last elements. I'm all for compilers getting smarter to help do what programmers expect, but when they can't do a very good job I don't find the tradeoff to be very appealing. Basically, I think pattern matching is a much better solution this problem, rather than repurposing syntax that technically means something else (even though 90% of the time people who reach for it mean to do the other thing). Also, fwiw, I was mostly talking about things like the last example you gave. I guess it would be possible that in circumstances where T is invalid but T? would be valid, the language actually silently undos the refinement to make that code work. However, I am not sure this is actually a positive, and it doesn't help with the ambiguous cases anyways.