В теории типов вариантность описывает отношение между двумя обобщёнными типами (дженериками). Например, в каких обстоятельствах родительский тип может быть заменён дочерним, а в каких — нет, и так далее.
На эту тему можно найти множество ресурсов, особенно таких, где всё описано длинно и сложным, формально-архитектурным языком. Мне бы хотелось создать короткую и простую памятку (с небольшими вкраплениями формализмов), к которой можно легко вернуться, если вдруг забудутся детали.
Ковариантность
Отношение ковариантности представляет собой обычное отношение подтипа, когда более Узкий/Дочерний тип может использоваться там, где ожидается более Широкий/Родительский тип. Например:
Я могу поставить Кошку туда, где может стоять любое Животное
Но я не могу поставить любое Животное туда, где может стоять только Кошка
class Animal { genus: string; } class Cat extends Animal { clawSize: number; } function move(animal: Animal) {} function meow(cat: Cat) {} move(cat) // Любая кошка может двигаться meow(animal) // Не каждое животное умеет мяукать
Точнее: Вы можете использовать B там, где ожидается A, если B < A.
// V — это позиция возвращаемого значения (выход) type Covariant<V> = () => V; // Где Animal — широкий тип (W), а Cat — узкий (N) function covariance( covW: Covariant<Animal>, covN: Covariant<Cat>, ) { covW = covN; // OK. Функция, возвращающая кошку, может заменить функцию, возвращающую животное. covN = covW; // Ошибка! Нельзя быть уверенным, что функция, возвращающая животное, вернёт именно кошку. }
Контравариантность
Контравариантность — это противоположность ковариантности. Это, пожалуй, самый сложный для понимания тип вариантности. В случае контравариантности, когда ожидается Узкий/Дочерний тип, вместо него можно использовать Широкий/Родительский.
В каких обстоятельствах это может произойти? Представьте себе обработчик. Например, обработчик общего корма для животных, который обогащает его белком (допустим что это полезно для любого животного). И обработчик для кошачьего корма, который придаёт ему более рыбный вкус (глупо, но неважно).
Итак, можно ли обработать кошачий корм с помощью общего обработчика корма для животных? Конечно, больше белка кошке не навредит.
А можно ли обработать любой корм для животных с помощью обработчика кошачьего корма? Думаю, нет — не все любят рыбный вкус.
Повторим более формально:
Я могу обработать Кошачий корм так же, как обрабатывается любой корм для Животных.
Но я не могу обработать корм для Животных так же, как обрабатывается Кошачий корм.
class AnimalFood { // } class CatFood extends AnimalFood { // } function processAnimalFood(animalFood: AnimalFood): void { // Добавляем немного белка // } function processCatFood(catFood: CatFood): void { // Придаём рыбный вкус // } /** * Перед подачей обработаем корм */ function serveAnimalFood(processor: (food: AnimalFood) => void): void { const food = new AnimalFood(); processor(food); } function serveCatFood(processor: (food: CatFood) => void): void { const food = new CatFood(); processor(food); } // Мы не можем использовать обработчик кошачьего корма, чтобы подать корм для животного! // Не все животные любят рыбный вкус! serveAnimalFood(processCatFood); // Вы можете использовать обработчик корма для животных, чтобы подать кошачий корм. // Белок пойдет кошке на пользу serveCatFood(processAnimalFood);
В теории типов: Вы можете использовать обработчик для A там, где ожидается обработчик для B, если B < A.
type Contravariant<V> = (v: V) => void; // Где Animal — широкий тип (W), а Cat — узкий (N) function contravariance( contraW: Contravariant<Animal>, contraN: Contravariant<Cat>, ) { contraW = contraN; // Ошибка! Обработчик кошачей еды не может обработать любую еду. contraN = contraW; // OK! Обработчик общей еды справится и с кошачей. }
Инвариантность
С инвариантностью всё проще. Это представляет собой отсутствие взаимозаменяемости. В номинативных системах типов, например в С, это единственный вид вариантности. Реальный пример такого отношения можно найти в сортировке мусора.
Есть общее понятие Мусор и его разновидности, такие как Макулатура, Пищевые Отходы и т.д.
И если ваши отходы классифицированы, и для них есть подходящий контейнер, вы должны использовать этот и только этот контейнер.
При сортировке мусора нельзя выбрасывать отходы в общий контейнер, если их можно отсортировать.
Вы можете выбрасывать отходы только в контейнер соответствующего типа.
class Waste { readonly type = 'неперерабатываемый'; } class FoodWaste { readonly type = 'органика'; } function unrecycledBin(waste: Waste) {} function organicBin(waste: FoodWaste) {} unrecycledBin(new FoodWaste()); // Нельзя выбрасывать пищевые отходы в контейнер для неперерабатываемых! Надо быть молодцом! organicBin(new Waste()); // Нельзя выбрасывать несортированный мусор в контейнер для органики, вы что, преступник???
Формально: Вы можете использовать A только там, где ожидается A.
type Invariant<V> = (v: V) => V; function invariance( inW: Invariant<Animal>, inN: Invariant<Cat>, ) { inW = inN; // Ошибка! Типы не взаимозаменяемы. inN = inW; // Ошибка! То же самое. }
Бивариантность
Противоположность инвариантности. Бивариантность — это полная взаимозаменяемость, когда тип A можно заменить на B и наоборот.
В TypeScript бивариантность не распространена, но всё же встречается. Например, как выяснили ранее, параметры функций являются контравариантны. Но есть исключения: у методов параметры бивариантны.
type Bivariant<V> = { process(v: V): void; } function bivariance( biW: Bivariant<Animal>, biN: Bivariant<Cat>, ) { biW = biN; // OK! biN = biW; // OK! }
Такое поведение было выбрано создателями TypeScript для большей гибкости, хотя оно и является теоретически менее строгим. Его можно изменить с помощью явных аннотаций вариантности.
// Ключевое слово `in` в дженериках делает тип Контравариантным type ContravariantMethod<in V> = { process(v: V): void; } function contravariance( contraW: ContravariantMethod<Animal>, contraN: ContravariantMethod<Cat>, ) { contraW = contraN; // Ошибка! Теперь это строгая контравариантность. contraN = contraW; // OK! }
Ссылки
ссылка на оригинал статьи https://habr.com/ru/articles/944074/
Добавить комментарий