Коваріантність і контраваріантність (програмування)
Варіантність — у програмуванні, спосіб перенесення успадкування типів на нові типи, параметризовані попередніми (контейнери, узагальнені типи, делегати).
Терміни виникли від термінів теорії категорій «коваріантний» та «контраваріантний» функтор.
Визначення
В системі типів мови програмування, конструктор типів є:
- коваріантним — якщо він зберігає порядок типів (від похідних до базового);
- контраваріантним — якщо він змінює порядок типів на протилежний;
- біваріантним — якщо прямий і обернений порядок справедливі одночасно.
- інваріантним чи неваріантним — якщо він не підпадає під попередні варіанти.
Приклади на C#
Наприклад, у C#, якщо Cat це підтип Animal, тоді:
IEnumerable<Cat>це підтипIEnumerable<Animal>. Підтипність збережено, боIEnumerable<T>коваріантний заT.Action<Animal>підтипAction<Cat>. Підтипність обернено, боAction<T>контраваріантний заT.- Ані
IList<Cat>, аніIList<Animal>не є підтипом іншого, боIList<T>інваріантний заT.
Варіантність узагальнених інтерфейсів C# визначається через використання атрибута out (коваріантний) або in (контраваріантний) для (нуля чи більше) його параметрів типів. Для кожного промаркованого в такий спосіб параметра, компілятор перевіряє, не дозволячи жодного порушення, що використання глобально цілісне. Згадані інтерфейси означено як IEnumerable<out T>, Action<in T> і IList<T>. Типи з більш ніж одним параметром-типом можуть вказувати різні варіантності для різних параметрів-типів. Наприклад, тип-делегат Func<in T, out TResult> представляє функцію з контраваріантним входовим параметром типу T і коваріантним параметром типу результату TResult.[1]
Варіантність контейнерів
Якщо типи Cat та Dog наслідують тип Animal, конструктором масивів з типу Animal утворимо тип Animal[] («масив звірів»). Ми можемо використовувати його:
- коваріантно: вважаючи що
Cat[]є представникомAnimal[] - контраваріантно: вважаючи що
Animal[]є представникомCat[] - інваріантно: вважаючи що
Animal[]не є представникомCat[]а такожCat[]не є представникомAnimal[].
Залежно від типу операцій, що будуть проводитись над масивом ми можемо використовувати різну варіантність:
- якщо тільки читати елементи масиву, то безпечно використовувати його коваріантно, оскільки
Catє представникомAnimal; - якщо тільки записувати чи передавати елементи масиву то безпечно використовувати його контраваріантно, щоб не було можливості в масив
Cat[]записати елемент типуDog; - якщо ж потрібно читати та записувати елементи масиву, то безпечно використовувати його тільки інваріантно. Також можна реалізувати два незалежні інтерфейси коваріантний Producer<T> для читання та контраваріантний Consumer<T> для запису елементів.
Масиви є коваріантними в Java, C# та інваріантними в C++.
Варіантність функційних типів
Функційні типи є коваріантними за типом результату та контраваріантними за типами аргументів.
Тобто функції типу Cat->Cat та Animal->Animal успадковують тип Cat->Animal та можуть безпечно використовуватись там де очікувався базовий тип.
Варіантність при успадковуванні
C++, починаючи зі стандарту 1998 року, підтримує коваріантність при заміщенні віртуальних методів:
class X {};
class Y : public X {};
class A
{
public:
virtual X* f() { return new X; }
};
class B : public A
{
public:
virtual Y* f() { return new Y; } // заміщуючи метод, можна повернути уточнений тип результату
};
В таблиці описано варіантність при заміщенні методів для різних мов програмування:
| тип аргумента | тип результату | |
|---|---|---|
| C# | інваріантний | інваріантний |
| C++ (з 1998), Java (з J2SE 5.0), Scala, D | інваріантний | коваріантний |
| Sather | контраваріантний | коваріантний |
| Eiffel | коваріантний | коваріантний |
Варіантність шаблонних типів
В деяких мовах програмування, що підтримують параметричний поліморфізм, можливо вказати варіантність шаблонного типу за параметром. Це можливо:
- при оголошенні шаблонного типу (як в C#) чи
- при створенні (використанні) конкретного типу на основі шаблонного (як у Java).
Задання варіантності при оголошенні
- в C# це можливо тільки для інтерфейсів (ключовими словами
outтаin), - в Scala та OCaml (ключовими словами
+та-).
Якщо варіантність не задано, то тип вважатиметься інваріантним.
- Приклад
interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
}
IEnumerator<Cat> вважатиметься успадкованим від IEnumerator<Animal>.
Задання варіантності при використанні
Задання варіантності при створенні кожного конкретного типу є гнучкішим. Воно дає змогу автору класу не оголошувати декілька інтерфейсів з різною варіантністю. В Java варіантність задається разом із заданням обмежувального типу:
List<? extends Animal>буде коваріантним типу елемента;List<? super Animal>буде контраваріантним типу елемента.
При заданні варіантності буде заборонено використовувати методи класу з різною варіантністю.
Примітки
- ↑ Func<T, TResult> Delegate [Архівовано 19 Серпня 2017 у Wayback Machine.] — MSDN Documentation
Джерела
- http://msdn.microsoft.com/ru-ru/library/dd799517.aspx [Архівовано 24 Грудня 2015 у Wayback Machine.] Коваріантність і контраваріантність в шаблонах.
- http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx [Архівовано 28 Червня 2013 у WebCite] Covariance and Contravariance in C#, Part Two. Eric Lippert.
Джерело: стаття у Вікіпедії та історія редагувань (автори).