Под ковариацией / контравариантностью обычно подразумевают это.Предположим, что X
, Y
, Z
являются типами.Предположим далее, что a → b
обозначает тип функции с аргументом типа a
и результатом типа b
.<:
обозначает отношение подтипа или, возможно, какое-то другое понятие «соответствия».Стрелка ⇒ гласит «влечет за собой».Тогда справедливо следующее:
X <: Y ⇒ (Z → X) <: (Z → Y)
X <: Y ⇒ (Y → Z) <: (X → Z)
То есть конструктор типа функции ковариантен относительно типа результата (источник данных) и контравариантен относительно типа аргумента (приемник данных).Это основной факт, и вы более или менее не можете сделать что-то слишком креативное, например, поменять направление стрелок.Конечно, вы всегда можете использовать не-дисперсию вместо ко-или контравариантности (большинство языков используют).
Типы объектов могут быть канонически закодированы с типами функций, поэтому здесь тоже не так много свободыКаждый параметр типа представляет либо источник данных (ковариантный), либо приемник данных (контравариантный), либо оба (новариантный).Если это звучит и противоречиво на одном языке, то на другом оно будет либо противоречивым, либо необоснованным.
Я думаю, что Scala довольно близок к идеальному языку в этом отношении.Вы приводите пример, который очень похож на Scala, поэтому вы, скорее всего, знакомы с языком.Интересно, почему вы думаете, что его система типов работает хорошо только в некоторых случаях.Каковы другие примеры?
Одна теоретическая работа, которую должен прочитать каждый начинающий разработчик языка, - «Теория объектов» Луки Карделли.