4 ms·
Discriminated unions [1] are what you're looking for here. Contrary to what the article claims, the functions shown are not 100% type safe, because if you defin
by peterkelly 8y ago
Discriminated unions [1] are what you're looking for here. Contrary to what the article claims, the functions shown are not 100% type safe, because if you define the following:
interface NotActuallyWizard extends Person {
type: PersonType.Wizard;
otherStuff: string;
}
and then write:
const JohnSmith: NotActuallyWizard = {
type: PersonType.Wizard,
name: 'John Smith',
otherStuff: 'whatever',
};
getSpellsForPerson(JohnSmith);
it will pass the type checker (isWizard returns true because it doesn't take into account the possibility that another interface may have a 'type' property of PersonType.Wizard) but the code will fail at runtime.
Instead, you should define the interfaces as follows:
interface PersonBase {
name: string;
}
interface Wizard extends PersonBase {
type: 'Wizard';
spells: string[];
}
interface Muggle extends PersonBase {
type: 'Muggle';
dursley: boolean;
}
type Person = Wizard | Muggle;
then replace the type guards with:
if (person.type !== 'Wizard') {
and
if (person.type !== 'Muggle') {
This will report an error for getSpellsForPerson(JohnSmith) because the compiler knows that (1) the only possibilities for Person are Wizard and Muggle, (2) both have type properties (3) the type properties have distinct literal values, and therefore (4) a Person with a type property of 'Wizard' has must have a spells property and a Person with a type property of 'Muggle' must have a dursley property.
As explained in [1], you can also take advantage of these with switch statements, and the compiler will do an exhaustiveness check so you catch any cases where you add a new type but forget to update one of your switches.
[1] https://basarat.gitbooks.io/typescript/docs/types/discriminated-unions.html https://basarat.gitbooks.io/typescript/docs/types/discrimina...
- fingerlocks 8y agoThe problem is not the use of type guards, but rather the author's implementation of them. The entire point of type guards is to narrow a variable's type within a block- e.g. to avoid accessing undefined members. If we rewrite isWizard() to check for the existence of the `spells` variable, your counterexample will fail the type guard and JohnSmith will be exposed as the NotActuallyWizard he really is.
- andy_ppp 8y agoAlmost as if Typescript is so complicated that getting your types "correct" is nearly impossible, especially when you add in the high variability in the quality of provided types on DefinitelyTyped [1]. [1] https://github.com/DefinitelyTyped/DefinitelyTyped https://github.com/DefinitelyTyped/DefinitelyTyped
- moomin 8y agoI don't really agree with this take. The point is that TypeScript allows you to type standard JavaScript idioms. The whole "type equals string" thing is a standard JavaScript idiom. Are discriminated unions better? Yes. But we are where we are and JavaScript is complicated.
- andy_ppp 8y agoThere are lots of better languages that compile to JS and don’t let you make mistakes in the same way the two grand parents do. Just saying JS is complicated to make typed is completely true and not a good reason to use TS; it doesn’t excuse the fact that I spend all this time doing a Typescript dance and still wonder if my Types have weird flaws. I would encourage people to consider Reason or Dart or Elm or even plain old ES6 before they decide Typescript solves their problem.
- vga805 8y agoIf anyone gives these other compile-to-js languages a look, I would add ClojureScript to that list, especially if doing React work. The re-frame framework is great, and the cljs ecosystem is a joy imo. You won't get the kind of type system these other langs provide, but there's some interesting stuff going on with spec.
- andy_ppp 8y agoAgree strongly ClojureScript - shame it has a terrible name which probably means it won't gain the popularity it deserves. I've also heard good things about PureScript.