Subtypen en variantie
Q# ondersteunt slechts enkele conversiemechanismen. Impliciete conversies kunnen alleen plaatsvinden bij het toepassen van binaire operators, het evalueren van voorwaardelijke expressies of het samenstellen van een letterlijke matrix. In deze gevallen wordt een algemeen supertype bepaald en worden de benodigde conversies automatisch uitgevoerd. Afgezien van dergelijke impliciete conversies, zijn expliciete conversies via functie-aanroepen mogelijk en vaak nodig.
Op dit moment is de enige subtyperelatie die bestaat van toepassing op bewerkingen. Intuïtief is het logisch dat men een bewerking mag vervangen die meer dan de vereiste set functors ondersteunt. Concreet is voor twee concrete typen TIn
en TOut
, de subtyperelatie
(TIn => TOut) :>
(TIn => TOut is Adj), (TIn => TOut is Ctl) :>
(TIn => TOut is Adj + Ctl)
waarbij A :> B
aangeeft dat B
een subtype van A
is. Anders geformuleerd, B
is beperkender dan A
zodanig dat een waarde van het type B
kan worden gebruikt waar een waarde van het type A
is vereist. Als een aanroepbare afhankelijk is van een argument (item) van het type A
, kan een argument van het type B
veilig worden vervangen, omdat indien alle benodigde mogelijkheden biedt.
Dit type polymorfisme breidt zich uit tot tuples, omdat een tuple van het type B
een subtype van een tuple-type A
is als het hetzelfde aantal items bevat en het type van elk item een subtype is van het bijbehorende itemtype in A
. Dit wordt dieptesubtypering genoemd. Er is momenteel geen ondersteuning voor subtypen voor breedte, dat wil gezegd dat er geen subtyperelatie is tussen twee door de gebruiker gedefinieerde typen of een door de gebruiker gedefinieerd type en een ingebouwd type. Het bestaan van de unwrap
operator, waarmee u een tuple met alle benoemde en anonieme items kunt extraheren, voorkomt dit.
Notitie
Met betrekking tot aanroepbare gegevens geldt dat als een aanroepbare een argument van het type A
verwerkt, het ook in staat is om een argument van het type B
te verwerken. Als een aanroepbare wordt doorgegeven als een argument aan een andere aanroepbare, moet deze alles kunnen verwerken wat de typehandtekening vereist. Dit betekent dat als de aanroepbare een argument van het type B
moet kunnen verwerken, elk aanroepbaar dat een meer algemeen argument van het type A
kan verwerken, veilig kan worden doorgegeven. Omgekeerd verwachten we dat als we vereisen dat de doorgegeven aanroepbare een waarde van het type A
retourneert, de belofte om een waarde van het type B
te retourneren voldoende is, omdat die waarde alle benodigde mogelijkheden biedt.
Het bewerkings- of functietype is contravariant in het argumenttype en covariant in het retourtype.
A :> B
impliceert dus dat voor elk concreet type T1
,
(B → T1) :> (A → T1), and
(T1 → A) :> (T1 → B)
waarbij →
hier een functie of bewerking kan worden bedoeld en we eventuele aantekeningen voor kenmerken weglaten.
Het A
vervangen door (B → T2)
respectievelijk en (T2 → A)
, en het B
vervangen door (A → T2)
respectievelijk en (T2 → B)
, leidt tot de conclusie dat voor elk concreet type 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))
Door inductie volgt dat elke extra indirectie de variantie van het argumenttype omdraait en de variantie van het retourtype ongewijzigd laat.
Notitie
Dit maakt ook duidelijk wat het variantiegedrag van matrices moet zijn; Het ophalen van items via een operator voor itemtoegang komt overeen met het aanroepen van een functie van het type (Int -> TItem)
, waarbij TItem
het type van de elementen in de matrix is. Omdat deze functie impliciet wordt doorgegeven bij het doorgeven van een matrix, moet het itemtype van matrices covariant zijn. Dezelfde overwegingen gelden ook voor tuples, die onveranderbaar zijn en dus covariant zijn voor elk itemtype.
Als matrices niet onveranderbaar zouden zijn, zou het bestaan van een constructie waarmee u items in een matrix kunt instellen en dus een argument van het type TItem
gebruiken, impliceren dat matrices ook contravariant moeten zijn. De enige optie voor gegevenstypen die ondersteuning bieden voor het ophalen en instellen van items is daarom invariant, wat betekent dat er geen subtyperelatie is; B[]
is geen subtype van A[]
, zelfs als B
een subtype van A
is. Ondanks het feit dat matrices in Q#onveranderbaar zijn, zijn ze invariant in plaats van covariant. Dit betekent bijvoorbeeld dat een waarde van het type (Qubit => Unit is Adj)[]
niet kan worden doorgegeven aan een aanroepbare waarvoor een argument van het type (Qubit => Unit)[]
is vereist.
Het invariant houden van matrices biedt meer flexibiliteit met betrekking tot de manier waarop matrices worden verwerkt en geoptimaliseerd in de runtime, maar het is mogelijk om dit in de toekomst te herzien.