Коваріантність і контраваріантність

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

Варіантність — у програмуванні, спосіб перенесення наслідування типів на нові типи параметризовані попередніми (контейнери, узагальнені типи, делегати).

Терміни виникли від термінів теорії категорій «коваріантний» та «контраваріантний» функтор.

Визначення[ред. | ред. код]

В системі типів мови програмування, конструктор типів є:

  • коваріантним — якщо він зберігає порядок типів (від похідних до базового);
  • контраваріантним — якщо він змінює порядок типів на протилежний;
  • біваріантним — якщо прямий і обернений порядок є справедливими одночасно.
  • інваріантним чи неваріантним — якщо він не підпадає під попередні варіанти.

Варіантність контейнерів[ред. | ред. код]

Якщо типи Cat та Dog наслідують тип Animal, конструктором масивів з типу Animal утворимо тип 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> буде контраваріантним типу елемента.

При заданні варіантності буде заборонено використовувати методи класу з відмінною варіантністю.

Джерела[ред. | ред. код]