Совместимость типов на основе вариантности

Помимо того, что совместимость типов зависит от вида типизации, которая была подробно разобрана в главе “Экскурс в типизацию - Совместимость типов на основе вида типизации”, она также может зависеть от такого механизма, как вариантность.

Вариантность

Вариантность — это механизм переноса иерархии наследования типов на производные от них типы. В данном случае производные не означает связанные отношением наследования. Производные, скорее, означает определяемые теми типами, с которых переносится наследование.

Если вы впервые сталкиваетесь с этим понятием и определение вариантности кажется бессмысленным набором слов, то не стоит расстраиваться, эта тема очень простая, в чем вы сами скоро убедитесь.

В основе системы типов могут быть заложены следующие виды вариантности — ковариантность, контравариантность, инвариантность и бивариантность. Кроме того что система типов использует механизм вариантности для своих служебных целей, она также может предоставлять разработчикам возможность управлять им при помощи способов зависящих от конкретного языка.

Но прежде чем познакомиться с каждым из этих видов вариантности отдельно, стоит сделать некоторые уточнения касательно иерархии наследования.

Иерархия наследования

Иерархия наследования — это дерево, с расположенным вверху корнем или самый базовым типом (менее конкретный тип), ниже которого располагаются его подтипы (более конкретные типы). В случаях преобразования подтипа к базовому типу говорят, что выполняется восходящее преобразование (upcasting). И наоборот, когда выполняется приведение базового типа к его подтипу, говорят что выполняется нисходящее приведение (downcasting). Отношения между супертипом и его подтипом описываются как отношение родитель-ребенок (parent-child). Отношения между родителем типа и его ребенком описываются как предок-потомок (ancestor-descendant). Кроме того, при логическом сравнении тип находящийся выше по дереву, больше (>) чем тип находящийся ниже по дереву (и наоборот). Можно сказать, что parent > child, child < parent, ancestor > descendant, descendant < ancestor. Все это представлено на диаграмме ниже.

nominative types hierarchical tree

Ковариантность

Ковариантность — это механизм позволяющий использовать более конкретный тип там, где изначально предполагалось использовать менее конкретный тип. Простыми словами, совместимыми считаются типы, имеющие отношение A > B и A = B.

nominative types covariance compatible

Ковариантность не рекомендуется в местах, допускающих запись. Чтобы понять смысл этих слов, ниже представлена диаграмма, которая иллюстрирует, как через базовый тип можно добавить в массив с подтипом другой, несовместимый подтип и тем самым нарушить типобезопасность программы.

nominative types covariace bad example

Ковариантность рекомендуется применять в местах, допускающих чтение.

Контравариантность

Контвариантность — это противоположный ковариантности механизм позволяющий использовать менее конкретный тип там, где изначально предполагалось использовать более конкретный тип. Другими словами, совместимыми считаются типы имеющие отношения A < B и A = B.

nominative types contrvariance compatible

Контравариантность не рекомендуется в местах допускающих чтение, и наоборот, рекомендуется применять в местах допускающих запись.

Инвариантность

Инвариантность — это механизм позволяющий использовать только заданный тип. Совместимыми считаются только идентичные типы A = A.

nominative types invariance compatible

Бивариантность

Бивариантность — это механизм, который является представлением всех перечисленных ранее видов вариантности. В его случае совместимыми считаются любые из перечисленных ранее варианты типы A > B, A < B и A = B.

nominative types bivariance compatible

Бивариантность является самым нетипобезопасным видом вариантности.