Комментарии 8
Его можно изменить с помощью явных аннотаций вариантности.
Если вы почитаете документацию которую прикрепили то откроете для себя что так делать крайне не желательно. Там это написано выделенным шрифтом специально 5 раз, потому что аннотации вариантности нужны не для этого.
Документация предостерегает к необдуманному использованию, это верно. Однако, в случае что я привёл, прямо видно проблему которую создают бивариантные методы. Следующий код, являясь переписанной на классы аналогией секции про контраварианты, ошибку больше не показывает:
TypeScript playground
class AnimalFood {
protein: number = 0
}
class CatFood extends AnimalFood {
fishness: number = 0
}
abstract class Processor {
abstract process(food: AnimalFood): void;
}
class AnimalProcessor {
process(food: AnimalFood) {}
}
class CatProcessor {
process(food: CatFood) {}
}
const animalProcessor = new AnimalProcessor()
const catProcessor = new CatProcessor()
/**
* Перед подачей обработаем корм
*/
function serveAnimalFood(processor: AnimalProcessor): void {
const food = new AnimalFood();
processor.process(food);
}
function serveCatFood(processor: CatProcessor): void {
const food = new CatFood();
processor.process(food);
}
// оказывается, теперь всем по нраву рыбный вкус
serveAnimalFood(catProcessor);
serveCatFood(animalProcessor); Так что аннотации вариантности нужны именно для таких случаев. Тут in аннотация увеличит безопасность кода и предостережет от ошибки. Для этого они и созданы.
Давайте вместе прочитаем и переведём документацию:
Never write a variance annotation that doesn’t match the structural variance!
Никогда не используйте аннотации вариантности которые не совпадают со структурной вариантностью типа.
Variance annotations don’t change structural behavior and are only consulted in specific situations
Аннотации вариантности не изменяют структурную вариантность и проверяются только в специфических ситуациях.
Do NOT write variance annotations unless they match the structural behavior of a type
НЕ ИСПОЛЬЗУЙТЕ аннотации вариантности если только они не совпадают со структурным поведением типа.
Don’t try to use variance annotations to change typechecking behavior; this is not what they are for
Не пытайтесь использовать аннотации вариантности чтобы изменить поведение тайпчекера, они не для этого. (Написано дважды)
Буквально пишут что если аннотации вариантности меняют поведение тайпчекера значит они используются неправильно.
А вот собственно пример почему их не нужно так использовать (такой тоже есть в документации):
type ContravariantMethod<in V> = {
process(v: V): void;
}
declare let a: ContravariantMethod<Cat>
declare let b: ContravariantMethod<Animal>
a = b // Ок
b = a // Ошибка
b = { ...a } // Снова ок
И о чём этот пример говорит? Что spread оператор сбрасывает аннотации?
class Animal {
genus: number = 0
}
class Cat extends Animal {
clawSize: number = 0
}
type ContravariantMethod<in V> = {
process(v: V): void;
}
declare let _processCat_: ContravariantMethod<Cat>
declare let _processAnimal_: ContravariantMethod<Animal>
let processAnimal = (animal: Animal) => {}
let processCat = (cat: Cat) => {}
_processAnimal_ = _processCat_ // Ошибка
_processCat_ = _processAnimal_ // Ок
_processAnimal_ = { ..._processCat_ } // Снова ок, но потому что typescript сбросил аннотацию
processAnimal = processCat // Ошибка
processCat = processAnimal // Ок
processAnimal = { ...processCat } // Всё ещё ошибка, потому что функции всегда контравариантныВот буквально вырезка из этой же документации, которая приводит в пример эту же ситуацию, и прямо заявляет, что "measurement can be inaccurate"
Because variance is a naturally emergent property of structural types, TypeScript automatically infers the variance of every generic type. In extremely rare cases involving certain kinds of circular types, this measurement can be inaccurate. If this happens, you can add a variance annotation to a type parameter to force a particular variance:
// Contravariant annotation
interface Consumer<in T> {
consume: (arg: T) => void;
}Прекрасно сказано: "Никогда не используйте аннотации вариантности которые не совпадают со структурной вариантностью типа".
По вашему мой пример выше это НАРУШЕНИЕ структурной вариантности? Изменение вариантность с бивариантной, которую в области вариантности можно воспринять как any, которая в примере выше создают runtime ошибку, на контравариантность - стандартное поведение TypeScript с функциями, что от ошибки избавляет.
Какое тогда по вашему мнению правильное использование аннотаций, которое не меняет тайпчекер?
По вашему мой пример выше это НАРУШЕНИЕ структурной вариантности?
В вашем примере структурная и номинальная вариантности не совпадают, это ошибка. От этого предостерегает документация, потому что поведение тайпчекера в таких случаях не определено. Вы выстраиваете зависимость от нестабильной и незадокументированной детали реализации.
Какое тогда по вашему мнению правильное использование аннотаций, которое не меняет тайпчекер?
Основным применением аннотаций на данный момент считается оптимизация измерения вариантности в тайпчекере для повышения производительности в редакторе. Приходить к выводу что это необходимо следует только после анализа трейсов языкового сервера, выявившего значительную потерю производительности при измерении вариантности.
Так же аннотации могут использоваться в целях документирования вариантности параметров обобщённых типов, но это не совсем удобно потому что их легко можно случайно поставить неправильно.
Ещё у них упоминаются "крайне редкие случаи с рекурсивными типами" когда вариантность измеряется неправильно, но примеров таких случаев я пока лично не видел, это искать надо.

Кратко о вариантности с примерами на TypeScript