Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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"})



Sure, 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).


Idk, 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.


This 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: