Контравариантность просто означает «меняется в противоположном направлении» (а ковариация означает просто «меняется в одном и том же направлении»).В контексте отношений подтипов это относится к случаям, когда составной тип является подтипом другого типа, если-и-только-если одна его часть является супертипом той же части в другом типе.
Под «составным типом» я подразумеваю тип, имеющий другие типы компонентов.Такие языки, как Haskell, Scala и Java, обрабатывают это, заявляя, что у типа есть параметры (Java называет это «универсальными»).Из краткого обзора ссылки на документы Flow видно, что Flow не формализует параметры и фактически рассматривает тип каждого свойства как отдельный параметр.Поэтому я избегу специфики и просто расскажу о типах, состоящих из других типов.
Подтипирование - это все о заменяемости.Если кто-то хочет T
, я могу дать ему значение любого подтипа T
, и ничто не пойдет не так;то, что им «разрешено» делать с тем, о чем они просили, - это только то, что допустимо делать с любым возможным T
.Дисперсия возникает, когда типы имеют подструктуру других типов.Если кто-то запрашивает тип со структурой, включающей тип компонента T
, и я хочу дать ему значение с типом, который имеет такую же структуру, но тип компонента равен S
, когда это допустимо?
Если тип компонента существует, потому что они могут получить T
значений, используя запрашиваемый объект (например, чтение свойства или вызов метода, который возвращает значения T
),затем, когда я передам им свою ценность, они получат S
значений из нее вместо T
значений, которые они ожидали.Они захотят сделать что-то с этими значениями T
, что будет работать, только если S
является подтипом T
.Так что для составного типа я должен быть подтипом того, который они хотят, компонент того, который у меня есть, должен быть подтипом компонента в том, что они хотят.Это ковариация .
С другой стороны, если есть тип компонента, потому что они могут отправлять T
значений объекту, который они запрашивают (наподобие написания свойства или вызова метода, который принимает T
значений в качестве аргументов), то когда я передам им свое значение, он будет ожидать, что они отправят ему S
значения вместо T
единиц.Мой объект захочет сделать S
вещи со значениями T
, которые другой человек отправит ему.Это будет работать, только если T
является подтипом S
.Таким образом, в этом случае для составного типа I должен быть подтип того, который им нужен, компонент того, который у меня есть, должен быть супертипом компонента в одномони хотят.Это Контравариантность .
Простые типы функций - это конкретный пример, который обычно легко понять, не задумываясь.Тип функции, написанный в нотации Haskell, похож на ArgumentType -> ResultType
;сам по себе это составной тип с двумя типами компонентов, поэтому мы можем спросить, можно ли заменить один тип функции (является подтипом) другого типа функции.
Допустим, у меня есть список Dog
значения, и мне нужно сопоставить функцию над ним, чтобы превратить его в список Cat
значений.Таким образом, функция, выполняющая сопоставление, ожидает, что я дам ей функцию типа Dog -> Cat
.
Могу ли я дать ей функцию типа GreyHound -> Cat
?Нет;функция отображения вызовет мою функцию для всех Dog
значений в списке, и мы не знаем, что все они являются GreyHound
значениями.
Могу ли я дать ей функцию типа Mammal -> Cat
?Да;Моя функция может выполнять только те действия, которые действительны для любого Mammal
, что, очевидно, включает в себя все значения Dog
в списке, к которому она будет вызываться.
Могу ли я дать ей функцию типа Dog -> Siamese
?Да;функция отображения будет использовать значения Siamese
, возвращаемые этой функцией, для построения списка Cat
, а значения Siamese
являются Cat
значениями.
Можно ли дать ему функцию типа Dog -> Mammal
? Нет; эта функция может превратить Dog
в Whale
, который не помещается в список Cat
, который требуется построить функции отображения.