Hacker News new | ask | show | jobs
by brabel 1154 days ago
The problem with your TS code is that it's using covariance on a mutable generic type, which is unsafe and strict type systems would've forbidden that.

To expand: TS treats `Dog[]` as a subtype of `Animal[]` because `Dog` is a subtype of `Animal`... that work if you only read values from the array... but trying to change the array, you run into trouble. Some languages let you declare covariance (reading ) and contravariance explicitly to address this issue. To my limited knowledge of TS, that's not possible in TS (as it tries to keep things simple and compatible with JS, probably).

The answers in this[1] SO question explain these concepts better than I could.

[1] https://stackoverflow.com/questions/27414991/contravariance-...

1 comments

Why was the `dogs` array initialized as an `Animal[]` type instead of `Dog[]` type which would forbid the addition of a `Cat` type?

Why would you be able to map a call to `woof` over an `Animal[]` when `Animal` doesn't implement `woof`? I don't understand how the SO link answers these questions.

> Why was the `dogs` array initialized as an `Animal[]` type instead of `Dog[]`

That might be your confusion: it wasn't. Its type is `Dog[]`.

> which would forbid the addition of a `Cat` type?

Why would that be forbidden? The problematic method is `append_animals`, which only cares that both arguments satisfy `Animal`, which both `Dog` and `Cat` do.

> Why would you be able to map a call to `woof` over an `Animal[]` when `Animal` doesn't implement `woof`

Back to your root confusion, since for all intents and purposes, `dogs.map` thinks it's an array of dogs, it doesn't complain.

If `append_animals` was written like this, things would be fine:

    let append_animals = <T extends Animal>(animals: T[], animal: T) => animals.push(animal)
I see what you're saying. Thanks for taking the time to explain.