1

According to docs, "Type compatibility in TypeScript is based on structural subtyping". So this is possible:

type Person: {
  name: string;
}

const developer = {
  name: 'Joe',
  language: 'Typescript',
}

// this is ok because Person is a subtype of typeof developer
const otherDeveloper: Person = developer; // who writes code like that?!

This has many consequences, one of many is that you lose type information when using Object.keys:

// "keys" type is array of strings not `name` literal as this would be of course wrong because in runtime "keys" is ['name', 'language']
const keys = Object.keys(otherDeveloper); 

So I was trying to find the reason for this subtyping in TS docs as they promise but I couldn't find one

The places where TypeScript allows unsound behavior were carefully considered, and throughout this document we’ll explain where these happen and the motivating scenarios behind them.

The only place where this maybe would be helpful for me would be a function that expects object of narrower type, for example:

function getName(person: Person) {
  return person.name;
}

getName(developer); // works fine because of subtyping

I personally don't see a big issue if you'd have to use casting in that case:

getName(developer as Person);

Are there other examples that I might be missing?

apieceofbart
  • 2,048
  • 4
  • 22
  • 30
  • 2
    https://github.com/Microsoft/TypeScript/issues/12936 – zerkms Mar 15 '19 at 10:21
  • 1
    Well, this is called polymorphism and is the fundamental principal of all OO languages. It basically models the reality of life. Any person can register to a show. A developer is a person. He/she has more properties (like the computing languages he/she knows) than a "basic" person. But the show doesn't care. All it cares about is if you're a person. – JB Nizet Mar 15 '19 at 10:24
  • 1
    Without subtyping nothing really works, it's fundamental to any object oriented approach, even in JS you often pass in an object with more fields to a function that makes use only of a few fields. The weird part is when TS does not allow a subtype to replace a base type such as when it warns about excess property checks (`getName( { name: 'Joe', language: 'Typescript', })` is an error). Excess property checks are contrary to OO but are useful to catch common mistakes. – Titian Cernicova-Dragomir Mar 15 '19 at 10:27
  • 1
    @JBNizet I think the problem stated here is that there's no explicit subclass defined for `Developer` but the code still works with extra properties on the `developer` object even though that object is of type `Person` something that would fail on strongly typed languages. – apokryfos Mar 15 '19 at 10:29
  • 3
    @apokryfos Yeah .. the fun surprise of structural typing every C#/C++/Java developer has when moving to TS. It's strongly typed just not the way you think it's strongly typed. – Titian Cernicova-Dragomir Mar 15 '19 at 10:32
  • Thank you for your comments. I am no expert on OO but please note that is not a general rule - even TS docs mention this is no possible in Java or C#. I am not saying subtyping is wrong or anything but it's weird for me that it works implicitly. My understanding was that Typescript tried to "bend' towards JS world and I personally don't find this used often. As opposed to subtyping in function parameters, which is somehow derived from the definitions of core functions in javascript (like map for example) – apieceofbart Mar 15 '19 at 10:33
  • 1
    @apieceofbart The reason ts uses structural typing is that JS is duck typed. So you could do what you wrote above in JS, ideally you can do it in TS just in a more type safe way. Moreover since JS has object literals that do not belong to a specific class it would be difficult to specify inheritance relations explicitly everywhere. Making type relations explicit would have make TS less atractive to JS devs. We can get JS devs hooked with the idea that you don't need to type anything and under relaxed compiler rules you get some benefits, first dose is free :P, the hard stuff comes later ;) – Titian Cernicova-Dragomir Mar 15 '19 at 11:01
  • @TitianCernicova-Dragomir very nice comment - I think it would make a great answer ;) – apieceofbart Mar 15 '19 at 11:30

1 Answers1

4

The reason Typescript uses structural typing is that JS is duck typed.

So you could do what you wrote above in JS, ideally you can do it in TS just in a more type safe way. Javascript does not care about the declared types of object, there is no such concept in JS, it only cares about the properties objects have at runtime. Thus any object could be passed into your getName function and as long as the name property existed, the function would arguably function correctly.

Moreover since JS has object literals that do not belong to a specific class it would be difficult to specify inheritance relations explicitly everywhere. Making type relations explicit would have make TS less atractive to JS developers. Under a structural type system the types mostly work our and you get a lot of benefit from it without having to be very explicit.

There are ways to make get around structural typing and mimic a normative type in typescript either by using private properties (ex) or using branded types (ex)

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357