4 ms·
It's not the same op though. One is a check, the other creates an in-context copy that's unwrapped from the optional. The compiler of course optimizes this out
by RetpolineDrama 3y ago
It's not the same op though. One is a check, the other creates an in-context copy that's unwrapped from the optional. The compiler of course optimizes this out but it's not the same.
- refulgentis 3y agoRight. In Dart they are the same. In Swift they are distinct.
- mckn1ght 3y agoThey 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.
- teho 3y agoIn Dart if you want to narrow down a nullable final class variable, it is not enough to do if (value == null) { // value is still nullable here } instead you need to first capture the value final value = this.value if (value == null) { // value is non-null } Swift's guard and if let syntax seems way nicer here. In swift, it's also nice to destructure nested nullable values using if let. if let value = object.object?.value { // value is non null } In dart you'd need to first assign this into a local variable and then do the null check which is unnecessarily verbose.
- mraleph 3y agoIn Dart 3 instead of declaring the variable and then comparing you can simply write an if-case pattern: if (value case final value?) { // value is a fresh final variable }
- teho 3y agoThe more you know, I had totally missed this.
- Someone 3y agoSo Dart is moving in the Swift direction, which has if let value = value { // a new binding named ‘value’ refers to value, unwrapped } or (newer syntax that I initially found even weirder than the older one, but both grew on me) if let value { // a new binding named ‘value’ refers to value, unwrapped } (The old version will remain, as it is more flexible, allowing such things as if let baz = foo.bar(42) { // a new binding named ‘baz’ refers to foo.bar(42), unwrapped } )