Aufrufbare Deklarationen

Aufrufbare Deklarationen oder aufrufbare Deklarationen, die in einem globalen Bereich deklariert werden, sind standardmäßig öffentlich sichtbar. Das heißt, sie können überall im selben Projekt und in einem Projekt verwendet werden, das auf die Assembly verweist, in der sie deklariert sind. Mit Zugriffsmodifizierern können Sie ihre Sichtbarkeit auf die aktuelle Assembly beschränken, damit Implementierungsdetails später geändert werden können, ohne Code zu verändern, der auf einer bestimmten Bibliothek basiert.

Q# unterstützt zwei Arten von aufrufbaren Variablen: Vorgänge und Funktionen. Im Thema Vorgänge und Funktionen wird der Unterschied zwischen den beiden erläutert. Q# unterstützt auch das Definieren von Vorlagen. Beispielsweise typparametrisierte Implementierungen für eine bestimmte aufrufbare Variable. Weitere Informationen finden Sie unter Typparameterisierungen.

Hinweis

Solche typparametrisierten Implementierungen dürfen keine Sprachkonstrukte verwenden, die auf bestimmten Eigenschaften der Typargumente angewiesen sind; es gibt derzeit keine Möglichkeit, Typbeschränkungen in Q# auszudrücken oder spezielle Implementierungen für bestimmte Typargumente zu definieren.

Aufrufbare Variablen und Funktoren

Q# ermöglicht spezialisierte Implementierungen für bestimmte Zwecke. Beispielsweise können Operationen in Q# implizit oder explizit die Unterstützung für bestimmte Funktoren definieren und damit auch die spezialisierten Implementierungen, die aufgerufen werden, wenn ein bestimmter Funktor auf diese aufrufbare Variable angewendet wird.

Ein Funktor ist in einem bestimmten Sinne eine Factory, die eine neue aufrufbare Implementierung definiert, die eine bestimmte Beziehung zu der aufrufbaren Variablen hat, auf die sie angewendet wurde. Funktoren sind mehr als herkömmliche Funktionen auf höherer Ebene, da sie Zugriff auf die Implementierungsdetails der aufrufbaren Variablen benötigen, auf die sie angewendet wurden. In diesem Sinne ähneln sie anderen Factorys, z. B. Vorlagen. Sie können auch auf typparameterisierte Aufrufbare angewendet werden.

Betrachten Sie den folgenden Vorgang: ApplyQFT:

    operation ApplyQFT(qs : Qubit[]) : Unit is Adj + Ctl {
        let length = Length(qs);
        Fact(length >= 1, "ApplyQFT: Length(qs) must be at least 1.");
        for i in length - 1..-1..0 {
            H(qs[i]);
            for j in 0..i - 1 {
                Controlled R1Frac([qs[i]], (1, j + 1, qs[i - j - 1]));
            }
        }
    }

Dieser Vorgang akzeptiert ein Argument vom Typ Qubit[] und gibt einen Wert vom Typ Unitzurück. Die Anmerkung is Adj + Ctl in der Deklaration von ApplyQFT gibt an, dass der Vorgang sowohl den Adjoint- als auch den Controlled-Funktor unterstützt. (Weitere Informationen finden Sie unter Vorgangsmerkmale). Der Ausdruck Adjoint ApplyQFT greift auf die Spezialisierung zu, die den angrenzenden von ApplyQFTimplementiert, und Controlled ApplyQFT greift auf die Spezialisierung zu, die die kontrollierte Version von ApplyQFTimplementiert. Zusätzlich zum Argument des ursprünglichen Vorgangs verwendet die kontrollierte Version eines Vorgangs ein Array von Steuerqubits und wendet den ursprünglichen Vorgang auf die Bedingung an, dass sich alle diese Steuerelementqubits im Zustand |1⟩ befinden.

Theoretisch sollte eine Operation, für die eine adjungierte Version definiert werden kann, auch eine kontrollierte Version haben und umgekehrt. In der Praxis kann es jedoch schwierig sein, eine Implementierung für die eine oder andere Version zu entwickeln, insbesondere für probabilistische Implementierungen, die einem Wiederholung-bis-Erfolg-Muster folgen. Aus diesem Grund können Sie mit Q# die Unterstützung für jeden Funktor einzeln deklarieren. Da die beiden Funktoren jedoch kommutieren, muss ein Vorgang, der die Unterstützung für beide definiert, auch eine Implementierung (normalerweise implizit definiert, d. h. vom Compiler generiert) haben, wenn beide Funktoren auf den Vorgang angewendet werden.

Es gibt keine Funktoren, die auf Funktionen angewendet werden können. Funktionen verfügen derzeit über genau eine Bodyimplementierung und keine weiteren Spezialisierungen. Diese Deklaration entspricht beispielsweise

    function Hello (name : String) : String {
        $"Hello, {name}!"
    }

für die folgende Syntax:

    function Hello (name : String) : String {
        body ... {
            $"Hello, {name}!"
        }
    }

Hier gibt body an, dass die gegebene Implementierung für den Standardkörper der Funktion Hello gilt. Dies bedeutet, dass die Implementierung aufgerufen wird, wenn vor dem Aufruf keine Funktoren oder andere Factorymechanismen angewendet wurden. Die drei Punkte in body ... entsprechen einer Compiler-Direktive, die angibt, dass die Argumentelemente in der Funktionsdeklaration kopiert und an dieser Stelle eingefügt werden sollen.

Die Gründe, die explizit angeben, wo die Argumente der übergeordneten aufrufbaren Deklaration kopiert und eingefügt werden sollen, sind zweifach: einer, es ist unnötig, die Argumentdeklaration zu wiederholen, und zwei, es wird sichergestellt, dass Funktoren, die zusätzliche Argumente erfordern, wie der Controlled Funktor, konsistent eingeführt werden können.

Wenn es genau eine Spezialisierung gibt, die die Implementierung des Standardtexts definiert, kann die zusätzliche Umschließung des Formulars body ... { <implementation> } weggelassen werden.

Rekursion

Aufrufbare Variablen in Q# können direkt oder indirekt rekursiv sein und in beliebiger Reihenfolge deklariert werden. Ein Vorgang oder eine Funktion kann sich selbst oder eine andere aufrufbare Variable aufrufen, die den Aufrufenden direkt oder indirekt aufruft.

Bei der Ausführung auf Quantenhardware kann der Stapelspeicher eingeschränkt sein, und Rekursionen, die diese Stapelspeicherplatzgrenze überschreiten, führen zu einem Laufzeitfehler.