Udostępnij za pośrednictwem


Tworzenie podtypów i wariancja

Q# obsługuje tylko kilka mechanizmów konwersji. Niejawne konwersje mogą wystąpić tylko podczas 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 elementów functor. W przypadku dwóch typów TIn i TOutrelacja podtypowania jest

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

gdzie A :> B wskazuje, że B jest podtypem A. Frazowane inaczej, B jest bardziej restrykcyjne niż A takie, że wartość typu B może być używana wszędzie tam, gdzie wymagana jest wartość typu A. Jeśli obiekt wywołujący opiera się na argumencie (elemencie) typu A, argument typu B można bezpiecznie zastąpić, ponieważ jeśli zapewnia wszystkie niezbędne możliwości.

Ten rodzaj polimorfizmu rozciąga się na krotki w tym, że krotka typu B jest podtypem typu krotki A, 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 struct lub typem struct i dowolnym typem wbudowanym. Istnienie operatora unwrap, który umożliwia wyodrębnianie krotki zawierającej wszystkie nazwane elementy, zapobiega temu.

Uwaga

Jeśli obiekt wywołujący przetwarza argument typu A, jest również w stanie przetworzyć argument typu B. Jeśli element wywołujący jest przekazywany jako argument do innego elementu wywołującego, 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 B, każde wywołanie, które może przetworzyć bardziej ogólny argument typu A można bezpiecznie przekazać. Z drugiej strony oczekujemy, że jeśli wymagamy, aby przekazane wywołanie zwracało wartość typu A, obietnica zwrócenia wartości typu B jest wystarczająca, ponieważ ta wartość zapewni wszystkie niezbędne możliwości.

Typ operacji lub funkcji jest kontrawariantny w typie argumentu i kowariantny w zwracanym typie. A :> B z tego względu oznacza, że w przypadku dowolnego typu T1,

    (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 (B → T2) i (T2 → A) oraz zastępowanie B odpowiednio (A → T2) i (T2 → B) prowadzi do wniosku, że dla dowolnego 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)) 

Indukując, następuje, że każda dodatkowa pośrednia odwraca wariancję typu argumentu i pozostawia wariancję zwracanego typu 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, wynika z tego, że tablice muszą być kowariantne w ich typie elementu. Te same zagadnienia dotyczą również krotki, które są niezmienne, a tym samym kowariantne w odniesieniu do każdego typu elementu. Jeśli tablice nie były niezmienne, istnienie konstrukcji, która pozwoliłaby ustawić elementy w tablicy, a tym samym podjąć argument typu TItem, oznaczałoby to, że tablice również muszą być kontrawariantne. Jedyną opcją dla typów danych, które obsługują pobieranie i ustawianie elementów, jest niezmienne, co oznacza, że nie ma żadnego podtypowania relacji; B[] nie jest podtypem A[], nawet jeśli B jest podtypem A. Pomimo faktu, że tablice w 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 elementu wywołującego, który wymaga argumentu typu (Qubit => Unit)[]. Utrzymywanie niezmienności tablic pozwala na większą elastyczność związaną ze sposobem obsługi i optymalizacji tablic w środowisku uruchomieniowym, ale może być możliwe poprawienie tego w przyszłości.