class Dog { woof(){} }
class Cat { meow(){} }
function f(a: Dog|Cat) {
if (a instanceof Dog) {
a.woof()
} else {
a.meow()
}
}
let dogish = {woof: ()=>{}}
f(dogish)
This compiles because dogish is structurally a dog, the type system allows instanceof to narrow the type but "dogish instanceof Dog" is actually false, so at runtime this will crash after trying to call meow on dogish.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"})
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).
type userId = string;
type subscriptionId = string;
const uid: userId = 'userA';
const sid: subscriptionId = uid; // compiler is OK with thisHere's how type aliases are usually documented: "Type aliases do not introduce new types. They are equivalent to the corresponding underlying types."
The person above did have a better example of the downside: You usually want something to nominally comply with a specific interface, not structurally. i.e. Just because something happens to have an `encrypt(u8[]): u8[]` method doesn't mean you want to accept it as an argument. (Go interfaces have a similar issue.)