4 ms·
Not sure I follow your argument, but I use union types extensively in TypeScript and I don't think they're horrendous at all. E.g. it's very common to define a
by hn_throwaway_99 3y ago
Not sure I follow your argument, but I use union types extensively in TypeScript and I don't think they're horrendous at all.
E.g. it's very common to define a helper type like
type Maybe<T> = null | undefined | T
and then with TypeScript's flow analysis I can easily determine when it's safe to call properties on a value, e.g.
const foo: Maybe<Foo> = ...
if (foo) {
// Typescript now guarantees foo is not null or undefined
// thus it's a Foo, so I can call any Foo methods on it
foo.bar();
}
- lmm 3y ago> Not sure I follow your argument, but I use union types extensively in TypeScript and I don't think they're horrendous at all. Have you ever used a language with a good implementation of sum types? > and then with TypeScript's flow analysis I can easily determine when it's safe to call properties on a value The problem isn't calling methods on null or undefined. The problem is when the value is null or undefined, but not for the reason you think (e.g. you think your code set it to null, but actually the generic code you called came back with null: T), and so you mix up the semantics.
- auggierose 3y agoNot the parent, but I have used Standard ML, OCaml, Scala and Swift, and I like union types as well. They are strictly more powerful than sum types. If I want to create an Option type such that Option<Option<T>> = Option<T>, then that's possible. With sum types only, it is not. Yes, this gives you Option<T> = T in situations where T = Option<S>. If I want to have a sum type, then I can just create a distinguishing "kind" field, as you do in TypeScript. The argument against union types would be, that they are an advanced feature compared to sum types, and sum types are simpler to use right. The argument against sum types is that they are moving the role of types from descriptive to prescriptive, and that is what leads to the problem Hickey is pointing out.
- rtpg 3y agoWhile 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
- deredede 3y agoOn the other hand, explicitly writing coercions from T to Option<T> gets tiring real fast. I'm sure it should be possible to design a typing+coercion algorithm that automatically coerces `x` into `Some(x)` in 99% of the cases that are actually unambiguous (either due to the type of `x` being known not to be an option or due to parametricity), which would be the best of both worlds.
- adwn 3y agoWhat happens in the following case? const foo: Maybe<boolean> = false; It seems to me that your code wouldn't behave they way you'd expect.
- ElFitz 3y agoThat’s because of the "truthy-ish" nonsense mess inherited from JavaScript. You could say the same with 0, "", NaN. That being said, I wonder what was the logic behind the decision to implement that.
- rezonant 3y agoCoercing nullishness/undefined-ness to false arises from languages like C. It is an unfortunate footgun, but not what most people think of when they think of Javascript's coercion weirdnesses. Typescript supports this because it is a strict superset of Javascript. They certainly could have made TS strict mode complain about it though.
- strken 3y agoIf you want to cover this particularly case, strict-boolean-expressions in tslint should catch it. That being said, I agree it's an unfortunate footgun and think tsc should yell at you for using non-boolean types in conditionals if strict is on.