Введение
Концепция языка программирования
Многие системы типов языков программирования поддерживают подтипирование. Например, если тип является подтипом типа , то выражение типа должно быть взаимозаменяемым везде, где используется выражение типа . Вариативность определяет, как подтипирование между более сложными типами соотносится с подтипированием между их компонентами. Например, как должен соотноситься список s с списком s? Или как должна соотноситься функция, возвращающая , с функцией, возвращающей ? В зависимости от вариативности конструктора типа, отношение подтипирования простых типов может быть сохранено, изменено на обратное или проигнорировано для соответствующих сложных типов. Например, в языке программирования OCaml "список кошек" является подтипом "списка животных", потому что конструктор типа списка ковариантен. Это означает, что отношение подтипирования простых типов сохраняется для сложных типов. С другой стороны, "функция из Animal в String" является подтипом "функции из Cat в String", потому что конструктор типа функции контравариантен по типу параметра. Здесь отношение подтипирования простых типов изменяется на обратное для сложных типов. Разработчик языка программирования будет учитывать вариативность при разработке правил типизации для языковых конструкций, таких как массивы, наследование и обобщенные типы данных. Делая конструкторы типов ковариантными или контравариантными вместо инвариантных, можно будет принять больше программ как корректно типизированные. С другой стороны, программисты часто считают контравариантность неинтуитивной, а точное отслеживание вариативности для предотвращения ошибок типов во время выполнения может привести к сложным правилам типизации. Чтобы сохранить систему типов простой и позволить использовать полезные программы, язык может рассматривать конструктор типов как инвариантный, даже если было бы безопасно считать его вариативным, или рассматривать его как ковариантный, даже если это может нарушить типобезопасность.
Резюме вариантов и наследственности
В следующей таблице суммированы правила переопределения методов в языках, рассмотренных выше. Тип параметра Тип возвращаемого значения C++ (с 1998 года), Java (с J2SE 5.0), D, C# (с C# 9) Инвариантный Ковариантный C# (до C# 9) Инвариантный Инвариантный Scala, Sather Контравариантный Ковариантный Eiffel Ковариантный Ковариантный
Общие типы
В языках программирования, поддерживающих обобщения (также известные как параметрический полиморфизм), программист может расширить систему типов новыми конструкторами типов. Например, интерфейс C# вроде `IReadOnlyList<T>` позволяет создавать новые типы, такие как `IReadOnlyList<string>` или `IReadOnlyList<int>`. Затем возникает вопрос о ковариантности или контравариантности этих конструкторов типов. Существуют два основных подхода. В языках с аннотациями дисперсии в месте объявления (например, C#), программист указывает предполагаемую дисперсию параметров типа при определении обобщенного типа. В языках с аннотациями дисперсии в месте использования (например, Java), программист вместо этого указывает дисперсию при инстанцировании обобщенного типа.
Аннотации о расхождении по месту декларирования
Наиболее популярными языками с аннотациями дисперсии объявления места являются C# и Kotlin (с использованием ключевых слов и ), а также Scala и OCaml (с использованием ключевых слов и ). C# разрешает аннотации дисперсии только для типов интерфейсов, в то время как Kotlin, Scala и OCaml разрешают их как для типов интерфейсов, так и для конкретных типов данных.
Этимология
Эти термины происходят из понятия ковариантных и контравариантных функторов в теории категорий. Рассмотрим категорию, объекты которой – типы, а морфизмы представляют отношение подтипа ≤. (Это пример того, как любое частично упорядоченное множество можно рассматривать как категорию.) Тогда, например, конструктор функционального типа принимает два типа, p и r, и создает новый тип p → r; таким образом, он отображает объекты в объекты в . Согласно правилу подтипирования для функциональных типов, эта операция меняет направление ≤ для первого параметра и сохраняет его для второго, следовательно, это контравариантный функтор по первому параметру и ковариантный функтор по второму.