Вариантность в TypeScript
Вопрос
Объясните ковариантность и контравариантность на примере TypeScript. Где TS здесь сознательно нестрог?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Ковариантность: `Dog[]` можно использовать как `Animal[]`, подтип сохраняется в позиции «выхода»
Контравариантность параметров: функция, принимающая `Animal`, подходит туда, где ждут обработчик `Dog`, но не наоборот
`strictFunctionTypes` включает это для типов функций, методы остаются бивариантными
Массивы ковариантны и при этом изменяемы: запись `Cat` в `Animal[]`, который на деле `Dog[]`, компилятор не поймает
Аннотации `in`/`out` (TS 4.7) явно задают вариантность параметров типа
Что могут спросить следом
- Почему методы оставили бивариантными?
- Как объявить обработчик событий, чтобы проверка была строгой?
- Что такое инвариантность и где она встречается?
Разбор
Вариантность отвечает на вопрос, как подтипы аргумента типа переносятся на составной тип. Если Dog подтип Animal, то readonly Dog[] подтип readonly Animal[]: это ковариантность, значения только «выходят». Для параметров функций наоборот: функция (a: Animal) => void безопасно подставляется туда, где ждут (d: Dog) => void, потому что умеет больше. Это контравариантность. TS проверяет её строго только с strictFunctionTypes и только для типов функций; методы бивариантны ради совместимости встроенных типов вроде Array.push. Вторая известная дыра: изменяемые массивы ковариантны, поэтому можно положить кошку в массив собак через ссылку Animal[]. Инвариантный параметр (и вход, и выход, например set и get одного T) не допускает подстановок ни в какую сторону. С TS 4.7 вариантность можно задать явно in/out, это и документация, и ускорение проверки.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.