4 ms·
Yeah don't do that. :) Do this: interface Dog { typeName: "Dog"; woof():void } interface Cat { typeName: "Cat"; meow():void } function isDog(a:Dog|C
by TOGoS 3y ago
Yeah don't do that. :)
Do this:
interface Dog { typeName: "Dog"; woof():void }
interface Cat { typeName: "Cat"; meow():void }
function isDog(a:Dog|Cat) : a is Dog {
return a.typeName == "Dog"; // Some casting may be required here
}
function f(a: Dog|Cat) {
if (isDog(a)) a.woof();
else a.meow();
}
let dogish : Dog = {typeName:"Dog", woof: ()=>{ console.out("Woof this is Dog")}};
f(dogish);
The neat thing about TypeScript's type system is that it's structural but you can pretty easily implement nominal types on top of it.
(And if you only need compile-time checks, you can make typeName nullable; {typeName?:"Dog"} != {typeName?:"Cat"})
- esrauch 3y agoSure, but opt-in nominal types that you describe is still something of a foot-gun that you have to be wary of since its easy to accidentally pass something that conforms to the structural type but violates whatever expectation. Like here's an example: type SpecificJsonBlob = {x: number}; function serialize(s: SpecificJsonBlob) { return JSON.stringify(s); } function addOneToX(s: SpecificJsonBlob) { s.x += 1 } [...] let o = { get x() { return 1 } } serialize(o) addOneToX(o) This compiles because the getter makes it structurally conform to the type, but the 'serialize' will surprisingly return just '{}' since JSON.stringify doesn't care about getters, and addOneToX(o) is actually just a runtime crash since it doesn't implement set x. These are runtime issues that would be precluded by the type system in a normal structural typed language. There's obviously other cases of ergonomic benefit to structural typing (including that idiomatic JS duck-typing patterns work as you'd hope), but I think its not unreasonable for someone to feel that it's their biggest problem with typescript (as grandparent OP did).
- ivxvm 3y agoIdk, I think it's extremely statistically rare coincidence for nominative typing to be a better default than structural. In other words, it's much more useful (by default) to have some function work on more types than be a little bit more type-safe for the exactly the type author had in mind when writing it. In my opinion, type-safety has diminishing returns and the most of its usefulness lies in trivial things, like passing a string instead of some object or array just as a typo, or writing a wrong field name when mapping one object to another, but nominative typing lies way beyond my imaginary line marking the zone when types become more of annoyance than help.
- black_puppydog 3y agoThis is pretty much what I ended up with (with a string const generic type param actually) but now I have this extra implementation detail that I need to add/remove at every service boundary. It just makes every serialization/deserialization more complicated and generally adds cruft.