Freigeben über


Untertypisierung und Varianz

Q# unterstützt nur wenige Konvertierungsmechanismen. Implizite Konvertierungen können nur auftreten, wenn binäre Operatoren angewendet, bedingte Ausdrücke ausgewertet oder ein Arrayliteral erstellt werden. In diesen Fällen wird ein allgemeiner Supertyp bestimmt, und die erforderlichen Konvertierungen werden automatisch durchgeführt. Abgesehen von solchen impliziten Konvertierungen sind explizite Konvertierungen über Funktionsaufrufe möglich und häufig erforderlich.

Derzeit gilt die einzige Subtypingbeziehung, die für Vorgänge vorhanden ist. Intuitiv ist es sinnvoll, einen Vorgang zu ersetzen, der mehr als die erforderlichen Funktoren unterstützt. Konkret ist für alle zwei konkreten Typen TIn und TOutdie Untertypisierungsbeziehung

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

wobei A :> B angibt, dass B ein Untertyp von Aist. Anders ausgedrückt, ist B restriktiver als A so, dass ein Wert vom Typ B verwendet werden kann, wo ein Wert vom Typ A erforderlich ist. Wenn ein aufrufbarer Wert auf ein Argument (Element) vom Typ Abasiert, kann ein Argument vom Typ B sicher ersetzt werden, da wenn alle erforderlichen Funktionen bereitgestellt werden.

Diese Art von Polymorphismus erstreckt sich auf Tupel, da ein Tupel vom Typ B ein Untertyp eines Tupeltyps A ist, wenn er dieselbe Anzahl von Elementen enthält und der Typ jedes Elements ein Untertyp des entsprechenden Elementtyps in Aist. Dies wird als tiefenuntertypingbezeichnet. Es gibt derzeit keine Unterstützung für -Untertyping, d. h. es gibt keine Untertypbeziehung zwischen zwei struct Typen oder einem struct Typ und einem integrierten Typ. Das Vorhandensein des unwrap-Operators, mit dem Sie ein Tupel extrahieren können, das alle benannten Elemente enthält, verhindert dies.

Hinweis

Wenn ein aufrufbares Argument Averarbeitet, ist es auch in der Lage, ein Argument vom Typ Bzu verarbeiten. Wenn ein aufrufbarer Wert als Argument an einen anderen aufrufbaren Übergeben wird, muss er in der Lage sein, alles zu verarbeiten, was die Typsignatur möglicherweise erfordert. Dies bedeutet, dass alle aufrufbaren Argumente, die in der Lage sind, ein Argument vom Typ Bverarbeiten zu können, alle aufrufbaren, die ein allgemeineres Argument vom Typ A verarbeiten können, sicher übergeben werden können. Im Gegensatz dazu erwarten wir, dass die übergebene aufrufbare Eigenschaft einen Wert vom Typ Azurückgibt, dann ist die Zusage, einen Wert vom Typ B zurückzugeben, da dieser Wert alle erforderlichen Funktionen bereitstellt.

Der Vorgangs- oder Funktionstyp ist kontravarianten in seinem Argumenttyp und kovarianten in seinem Rückgabetyp. A :> B bedeutet daher, dass für jede konkrete Art T1,

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

wobei hier entweder eine Funktion oder einen Vorgang bedeuten kann, und wir lassen Anmerkungen für Merkmale weg. Substituieren von A mit (B → T2) bzw. (T2 → A) und Substituieren von B durch (A → T2) bzw. (T2 → B) führt zu dem Schluss, dass für jede konkrete Art 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)) 

Durch die Induktion folgt, dass jede zusätzliche Dereferenzierung die Varianz des Argumenttyps umkehrt und die Varianz des Rückgabetyps unverändert lässt.

Hinweis

Dies macht auch klar, was das Varianzverhalten von Arrays sein muss; Das Abrufen von Elementen über einen Elementzugriffsoperator entspricht dem Aufrufen einer Funktion vom Typ (Int -> TItem), wobei TItem der Typ der Elemente im Array ist. Da diese Funktion beim Übergeben eines Arrays implizit übergeben wird, müssen Arrays in ihrem Elementtyp kovariant sein. Dieselben Überlegungen gelten auch für Tupel, die unveränderlich sind und somit in Bezug auf jeden Elementtyp kovariant sind. Wenn Arrays nicht unveränderlich wären, würde das Vorhandensein eines Konstrukts, das es Ihnen ermöglichen würde, Elemente in einem Array festzulegen, und daher ein Argument vom Typ TItemzu übernehmen, bedeuten, dass Arrays auch kontravariant sein müssen. Die einzige Option für Datentypen, die das Abrufen und Festlegen von Elementen unterstützen, besteht daher darin, invariantezu sein, was bedeutet, dass es keinerlei Subtypisierungsbeziehung gibt; B[] ist kein Untertyp von A[], auch wenn B ein Untertyp von Aist. Trotz der Tatsache, dass Arrays in Q#unveränderlichesind, sind sie invariant und nicht kovariant. Dies bedeutet beispielsweise, dass ein Wert vom Typ (Qubit => Unit is Adj)[] nicht an ein aufrufbares Element übergeben werden kann, das ein Argument vom Typ (Qubit => Unit)[]erfordert. Das Beibehalten von Arrays invariant ermöglicht mehr Flexibilität im Zusammenhang mit der Behandlung und Optimierung von Arrays in der Laufzeit, aber es kann möglich sein, dies in Zukunft zu überarbeiten.