Коваріантність і контраваріантність (програмування)

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

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

Визначення

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

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

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;
  • якщо тільки записувати чи передавати елементи масиву то безпечно використовувати його контраваріантно, щоб не було можливості в масив 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> буде контраваріантним типу елемента.

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

Джерела

  1. Func<T, TResult> Delegate - MSDN Documentation
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.