Что значит контрвариантные функции

Документация тайпскрипта

Under strictFunctionTypes function type parameter positions are checked contravariantly instead of bivariantly. For some background on what variance means for function types check out What are covariance and contravariance?.

Прочитал и это тоже, но не понял что же такое контрвариантность?


Ответы (1 шт):

Автор решения: Gleb Kemarsky

Ковариантность и контравариантность обобщенных интерфейсов:

Понятия ковариантности и контравариантности связаны с возможностью использовать в приложении вместо некоторого типа другой тип, который находится ниже или выше в иерархии наследования.

  • Ковариантность позволяет использовать более конкретный тип, чем заданный изначально.
  • Контравариантность позволяет использовать более универсальный тип, чем заданный изначально.

Таким образом, если мы создадим тип данных животные и его подтип собаки или кошки, то в наших функциональных типах разные параметры будут по разному реагировать на значение другого типа:

  • ковариантный (covariant) позволяет передать ему собак или кошек вместо животных, назначенных изначально;
  • контрвариантный (contravariant) примет животных вместо собак, но не наоборот;
  • бивариантный (bivariant) позволит оба варианта;
  • инвариантный (invariant) не допустит подмен, только предписанный тип данных.

Обычно параметры в тайпскрипте позволяют инвариантность, а со strictFunctionTypes параметр потребует контрвариантности. Поэтому если, например:

type Animal = 'cat1' | 'cat2' | 'dog1' | 'dog2' | 'elephant1' | 'elephant2';
type Dog = 'dog1' | 'dog2';

declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;

то для обычного параметра оба присвоения годятся (песочница):

f1 = f2; // Ok without --strictFunctionTypes
f2 = f1; // Ok

а cо strictFunctionTypes первое вызовет сообщение об ошибке (песочница):

f1 = f2; // Error with --strictFunctionTypes
f2 = f1; // Ok

Если же добавить третий подтип

type Cat = 'cat1' | 'cat2';

declare let f3: (x: Cat) => void;

то третье присвоение вызовет ошибку в обоих случаях — и со strictFunctionTypes, и без него:

f2 = f3; // Error

Интерфейсы

Для интерфейсов важно, есть ли между ними структурное отличие, просто факта наследования недостаточно.

Type Compatibility:

TypeScript’s structural type system was designed based on how JavaScript code is typically written. Because JavaScript widely uses anonymous objects like function expressions and object literals, it’s much more natural to represent the kinds of relationships found in JavaScript libraries with a structural type system instead of a nominal one.

Вот такой код не покажет ошибок (песочница):

interface Animal{}
interface Dog extends Animal{}
interface Cat extends Animal{}

declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;

f1 = f2;
f2 = f1;
f2 = f3;

А вот в таком коде будет одна ошибка без strictFunctionTypes или две ошибки, если этот параметр включён в настройках:

interface Animal{}
interface Dog extends Animal{
    breed: string;
}
interface Cat extends Animal{
    name: string;
}

declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;

f1 = f2; // Error with --strictFunctionTypes
f2 = f1; // Ok
f2 = f3; // Error

И текст ошибки:

Type 'Animal' is not assignable to type 'Dog'.

→ Ссылка