4 ms·
While I get what you're referring to, the issue of Typescript getting this mixed up is not a real issue. Why? If you write a function that takes T | null and r
by rtpg 3y ago
While I get what you're referring to, the issue of Typescript getting this mixed up is not a real issue.
Why? If you write a function that takes T | null and returns T, then you're going to need to filter out your null.
Now, Typescript isn't safe, so you can have f: (T | null) -> T and give it a (null | null) and its return will indeed be null (at least at the moment, see [0]).
But your function's type guards will, at one point, filter out the null case. You might try doing this through some assertion, but if you do the runtime check, it will in fact blow up at runtime.
So Typescript will allow invalid code, but if you are actually doing type assertions correctly you'll get a runtime error at the "type narrowing" step. Worse than static verification of course, but better than carrying around a null that you think is not null.
Anyways, yeah, the conclusion is that in TS at least, T | null narrowing to T doesn't mean that T is not null. TS is at least smart enough to handle that.
[0]: https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABGOA5EAbDAeAKgPgAoAPALkV0QB9lMMBKcygbwChEPEZhETEBeQbSz1EbThMRQAFgCc4Ad2QBTJQFFZ82YQBEYOgEId9dpwC+pjrOVQQspMVYXWUAJ4AHZYgDy72Ajx8AQpqYQxWVggEAGcoRABbAENXACNlXFkYCABrV3JffzBsfSwg-jCAbgiosFipADdyEoxglHQsQiTU9Myc13oqyJi4DGUAOgw4AHNdAFlXRGUwABNEesSMEC8YaMQdABoGk1YgA https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABGO...
- Nullabillity 3y agoYes, it is an issue, but not for the reasons you think. For example, take these two APIs: // returns null if post doesn't exist async function loadPost(id: string): Promise<Post | null>; interface ResourceState<T> { // null if no data has been loaded yet data: T | null; } function createResource<T>(load: Promise<T>): Observable<ResourceState<T>>; Both are largely designed to fit together (`createResource(loadPost(id))`), but if you do then you now have no idea whether `data` is null because the post couldn't be found, or because it's still being loaded. You would either have to use different nulls (null vs undefined) or have `ResourceState` box T somehow. §um types are safe from this because they can distinguish between `None` and `Some(None)`.
- rtpg 3y agoRight, I agree with that. I think in practice this is largely circumvented by ad-hocing tagged unions in these cases. Higher level, I think that it's fairly rare to see Optionals outside of the _very_ basic cases, and downstream of that you're actually not flinging around nulls or undefined as data. Instead everyone reaches for a kind attribute. Especially in cases like you're talking about (where there's this notion of not finding something, but also this notion of something still loading). Not to say the distinction doesn't matter, but TS feels well designed in the sense that all of its unsafety is in places that end up not coming up in many "normal" codebases
- lmm 3y ago> But your function's type guards will, at one point, filter out the null case. You might try doing this through some assertion, but if you do the runtime check, it will in fact blow up at runtime. The problem is the other side: if you filter out null then you accidentally end up filtering out part of the T case as well, when you only meant to filter out the case that was not T. More generally, I submit that if you're doing your types properly you never want to collapse T|S into something different from a sum type. You have code that handles T which you expect to handle the cases that come from the code that yields T. You have code that handles S which you expect to handle the cases that come from the code that yields S. When a T turns out to be an S, or vice versa, that can only ever be a nasty surprise, or at best some code that works by accident.
- LelouBil 3y agoShould T | null narrow to T | never inside a !== check ? I'm not sure what this would imply.