Tworzenie podtypów i wariancja
Q# obsługuje tylko kilka mechanizmów konwersji. Niejawne konwersje mogą wystąpić tylko w przypadku stosowania operatorów binarnych, obliczania wyrażeń warunkowych lub konstruowania literału tablicy. W takich przypadkach określa się wspólny supertyp, a niezbędne konwersje są wykonywane automatycznie. Oprócz takich niejawnych konwersji jawne konwersje za pośrednictwem wywołań funkcji są możliwe i często konieczne.
Obecnie jedyną relacją podtypowania, która istnieje, ma zastosowanie do operacji. Intuicyjnie warto zastąpić operację, która obsługuje więcej niż wymagany zestaw functorów. W przypadku dwóch typów TIn
betonowych i TOut
relacji podtypowania jest
(TIn => TOut) :>
(TIn => TOut is Adj), (TIn => TOut is Ctl) :>
(TIn => TOut is Adj + Ctl)
where A :> B
wskazuje, że B
jest podtypem .A
Frazowane inaczej, jest bardziej restrykcyjne niż A
takie, B
że wartość typu B
może być używana wszędzie tam, gdzie wymagana jest wartość typuA
. Jeśli obiekt wywołujący opiera się na argumencie (elemencie) typu , argument typu A
B
można bezpiecznie zastąpić, ponieważ jeśli zapewnia wszystkie niezbędne możliwości.
Ten rodzaj polimorfizmu rozszerza się na krotki w tym, że krotka typu jest podtypem typu B
A
krotki, jeśli zawiera taką samą liczbę elementów, a typ każdego elementu jest podtypem odpowiedniego typu elementu w A
. Jest to nazywane podtypowaniem głębokości. Obecnie nie ma obsługi podtypowania szerokości, czyli nie ma relacji podtypu między dwoma typami zdefiniowanymi przez użytkownika lub typem zdefiniowanym przez użytkownika i dowolnym typem wbudowanym.
unwrap
Istnienie operatora, który umożliwia wyodrębnianie krotki zawierającej wszystkie nazwane i anonimowe elementy, uniemożliwia to.
Uwaga
W odniesieniu do wywołań, jeśli wywołalny przetwarza argument typu A
, jest również w stanie przetworzyć argument typu B
. Jeśli obiekt wywołujący jest przekazywany jako argument do innego wywołania, musi być w stanie przetwarzać wszystkie elementy, których może wymagać podpis typu. Oznacza to, że jeśli obiekt wywołujący musi być w stanie przetworzyć argument typu , każde wywołanie, które może przetworzyć bardziej ogólny argument typuB
A
, można bezpiecznie przekazać. Z drugiej strony oczekujemy, że jeśli wymagamy, aby przekazany obiekt wywołujący zwracał wartość typu , obietnica zwrócenia wartości typu A
B
jest wystarczająca, ponieważ ta wartość zapewni wszystkie niezbędne możliwości.
Typ operacji lub funkcji jest kontrawariantny w typie argumentu i kowariancie w zwracaniu typu.
A :> B
w związku z tym oznacza, że w przypadku dowolnego typu T1
betonowego ,
(B → T1) :> (A → T1), and
(T1 → A) :> (T1 → B)
gdzie →
tutaj może oznaczać funkcję lub operację i pomijamy wszelkie adnotacje dla cech.
Podstawianie A
odpowiednio i (T2 → A)
(B → T2)
, i podstawianie B
odpowiednio i (A → T2)
(T2 → B)
, prowadzi do wniosku, że dla każdego konkretnego typu 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))
Przez indukcję następuje, że każda dodatkowa pośrednia odwraca wariancję typu argumentu i pozostawia wariancję typu zwracanego bez zmian.
Uwaga
Pozwala to również wyjaśnić, jakie jest zachowanie wariancji tablic; pobieranie elementów za pośrednictwem operatora dostępu do elementu odpowiada wywołaniu funkcji typu (Int -> TItem)
, gdzie TItem
jest typem elementów w tablicy. Ponieważ ta funkcja jest niejawnie przekazywana podczas przekazywania tablicy, następuje, że tablice muszą być kowariantne w typie elementu. Te same zagadnienia dotyczą również krotki, które są niezmienne, a tym samym niezmienne w odniesieniu do każdego typu elementu.
Gdyby tablice nie były niezmienne, istnienie konstrukcji, która umożliwiłaby ustawianie elementów w tablicy, a tym samym użycie argumentu typu TItem
oznaczałoby, że tablice również muszą być kontrawariantne. Jedyną opcją dla typów danych obsługujących pobieranie i ustawianie elementów jest niezmienność , co oznacza, że nie ma żadnej relacji podtypowania; B[]
nie jest podtypem A[]
, nawet jeśli B
jest podtypem A
. Pomimo faktu, że tablice w obiekcie Q# są niezmienne, są niezmienne, a nie kowariantne. Oznacza to na przykład, że nie można przekazać wartości typu (Qubit => Unit is Adj)[]
do wywołania, która wymaga argumentu typu (Qubit => Unit)[]
.
Utrzymywanie niezmienności tablic pozwala na większą elastyczność związaną z obsługą i optymalizacją tablic w czasie wykonywania, ale w przyszłości może być możliwe ich skorygowanie.