Поделиться через


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

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

В настоящее время единственное существующее отношение подтипизации относится к операциям. Представляется само собой разумеющимся, что должна существовать возможность замены операции, которая поддерживает больше функторов, чем необходимо. В частности, для любых двух конкретных типов TIn и TOut отношение подтипизации имеет следующий вид:

    (TIn => TOut) :>
    (TIn => TOut is Adj), (TIn => TOut is Ctl) :>
    (TIn => TOut is Adj + Ctl)

Здесь A :> B указывает на то, что B является подтипом A. Иначе говоря, тип B является более строгим по сравнению с A, то есть значение типа B можно использовать везде, где требуется значение типа A. Если вызываемому объекту требуется аргумент (элемент) типа A, его можно спокойно заменить аргументом типа B, так как он обладает всеми необходимыми возможностями.

Подобный полиморфизм касается и кортежей в том смысле, что тип кортежа B является подтипом для типа кортежа A, если он содержит то же число элементов и тип каждого его элемента является подтипом для типа соответствующего элемента в A. Это называется подтипизацией в глубину. В настоящее время подтипизация в ширину не поддерживается, то есть отношение подтипизации между любыми двумя пользовательскими типами или пользовательским типом и любым встроенным типом невозможно. Это связано с наличием оператора unwrap, который позволяет извлечь содержимое кортежа, включая все именованные и анонимные элементы.

Примечание

Что касается вызываемых объектов, если такой объект обрабатывает аргумент типа A, он также может обработать аргумент типа B. Если вызываемый объект передается в качестве аргумента в другой вызываемый объект, он должен иметь возможность обрабатывать все, что требуется согласно сигнатуре типа. Это означает, что если вызываемому объекту требуется возможность обработки аргумента типа B, можно безопасно передать любой вызываемый объект, который способен обработать более общий аргумент типа A. И наоборот, предполагается, что если передаваемый вызываемый объект должен возвращать значение типа A, возможности возврата значения типа B достаточно, так как это значение обеспечивает все необходимые возможности.

Тип операции или функции является контравариантным по типу аргумента и ковариантным по типу возвращаемого значения. Поэтому из A :> B следует, что для любого конкретного типа T1 верно следующее:

    (B → T1) :> (A → T1), and
    (T1 → A) :> (T1 → B) 

Здесь может быть функцией или операцией, а аннотации с характеристиками опущены. Подставив вместо A соответственно (B → T2) и (T2 → A), а вместо — B соответственно (A → T2) и (T2 → B), мы приходим к заключению, что для любого конкретного типа T2 верно следующее:

    ((A → T2) → T1) :> ((B → T2) → T1), and
    ((T2 → B) → T1) :> ((T2 → A) → T1), and
    (T1 → (B → T2)) :> (T1 → (A → T2)), and
    (T1 → (T2 → A)) :> (T1 → (T2 → B)) 

Согласно индукции каждое дополнительное косвенное обращение изменяет вариантность типа аргумента, а вариантность типа возвращаемого значения остается без изменений.

Примечание

Из этого также очевидно, каким должно быть поведение вариантности массивов. Получение элементов посредством оператора доступа к элементу соответствует вызову функции типа (Int -> TItem), где TItem — это тип элементов массива. Поскольку эта функция передается неявным образом при передаче массива, массивы должны быть ковариантными по типу элементов. То же самое верно в отношении кортежей, которые являются неизменяемыми и поэтому ковариантны относительно типа каждого элемента. Если бы массивы не были неизменяемыми, существование конструкции, позволяющей задавать элементы массива и, таким образом, принимающей аргумент типа TItem, подразумевало бы контравариантность массивов. По этой причине типы данных, поддерживающие получение и задание элементов, могут быть только инвариантными, то есть отношение подтипизации полностью отсутствует. B[]не является подтипом A[], даже если B — подтип A. Хотя массивы в Q# являются неизменяемыми, они инвариантны, а не ковариантны. Это означает, например, что значение типа (Qubit => Unit is Adj)[] нельзя передать в вызываемый объект, требующий аргумента типа (Qubit => Unit)[]. Инвариантность массивов обеспечивает большую гибкость работы с ними и их оптимизации во время выполнения, но в будущем подход может измениться.