Freigeben über


Typparametrisierungen

Q# unterstützt typparameterisierte Vorgänge und Funktionen. Die Q# Standardbibliotheken verwenden stark typ parametrisierte Aufrufe, um eine Vielzahl nützlicher Abstraktionen bereitzustellen, einschließlich Funktionen wie Mapped und Fold, die aus funktionalen Sprachen vertraut sind.

Um das Konzept der Typparameterisierungen zu motivieren, betrachten Sie das Beispiel der Funktion Mapped, die eine bestimmte Funktion auf jeden Wert in einem Array anwendet und ein neues Array mit den berechneten Werten zurückgibt. Diese Funktionalität kann perfekt beschrieben werden, ohne die Elementtypen der Eingabe- und Ausgabearrays anzugeben. Da die genauen Typen die Implementierung der Funktion Mappednicht ändern, ist es sinnvoll, diese Implementierung für beliebige Elementtypen zu definieren; wir möchten eine Factory oder Vorlage definieren, die aufgrund der konkreten Typen für die Elemente im Eingabe- und Ausgabearray die entsprechende Funktionsimplementierung zurückgibt. Dieser Begriff wird in Form von Typparametern formalisiert.

Konkretisierung

Jede Vorgangs- oder Funktionsdeklaration kann einen oder mehrere Typparameter angeben, die als Typen oder Teil der Typen der Eingabe oder Ausgabe des aufrufbaren Objekts oder beides verwendet werden können. Die Ausnahmen sind Einstiegspunkte, die konkret sein müssen und nicht typ parametrisiert werden können. Typparameternamen beginnen mit einem Teilstrich (') und können mehrmals in den Eingabe- und Ausgabetypen angezeigt werden. Alle Argumente, die demselben Typparameter in der aufrufbaren Signatur entsprechen, müssen denselben Typ aufweisen.

Ein typ parametrisierter aufrufbarer Wert muss konkretisiert werden, d. h., er muss mit den erforderlichen Typargumenten versehen werden, bevor es als Argument zugewiesen oder übergeben werden kann, sodass alle Typparameter durch konkrete Typen ersetzt werden können. Ein Typ wird als konkret betrachtet, wenn es sich um einen der integrierten Typen, einen struct Typ oder um einen konkreten Typ innerhalb des aktuellen Bereichs handelt. Im folgenden Beispiel wird veranschaulicht, was es bedeutet, dass ein Typ im aktuellen Bereich konkret ist und im folgenden ausführlicher erläutert wird:

    function Mapped<'T1, 'T2> (
        mapper : 'T1 -> 'T2,
        array : 'T1[]
    ) : 'T2[] {

        mutable mapped = new 'T2[Length(array)];
        for (i in IndexRange(array)) {
            mapped w/= i <- mapper(array[i]);
        }
        return mapped;
    }

    function AllCControlled<'T3> (
        ops : ('T3 => Unit)[]
    ) : ((Bool,'T3) => Unit)[] {

        return Mapped(CControlled<'T3>, ops); 
    }

Die Funktion CControlled wird im Microsoft.Quantum.Canon Namespace definiert. Es verwendet einen Vorgang op typs 'TIn => Unit als Argument und gibt einen neuen Vorgang vom Typ (Bool, 'TIn) => Unit zurück, der den ursprünglichen Vorgang anwendet, vorausgesetzt, ein klassisches Bit (vom Typ Bool) wird auf truefestgelegt; dies wird häufig als klassische kontrollierte Version von opbezeichnet.

Die Funktion Mapped verwendet ein Array eines beliebigen Elementtyps 'T1 als Argument, wendet die angegebene mapper-Funktion auf jedes Element an und gibt ein neues Array vom Typ 'T2[] mit den zugeordneten Elementen zurück. Sie wird im Microsoft.Quantum.Array Namespace definiert. Für den Zweck des Beispiels werden die Typparameter nummeriert, um zu vermeiden, dass die Diskussion verwirrender wird, indem die Typparameter in beiden Funktionen denselben Namen geben. Dies ist nicht erforderlich; Typparameter für unterschiedliche Aufrufablen haben möglicherweise denselben Namen, und der ausgewählte Name ist nur sichtbar und relevant innerhalb der Definition dieser aufrufbaren.

Die Funktion AllCControlled verwendet ein Array von Vorgängen und gibt ein neues Array zurück, das die klassischen kontrollierten Versionen dieser Vorgänge enthält. Der Aufruf von Mapped löst den Typparameter 'T1 in 'T3 => Unitauf, und der Typparameter 'T2 in (Bool,'T3) => Unit. Die auflösenden Typargumente werden vom Compiler basierend auf dem Typ des angegebenen Arguments abgeleitet. Wir sagen, dass sie implizit durch das Argument des Aufrufausdrucks definiert sind. Typargumente können auch explizit angegeben werden, wie für CControlled in derselben Zeile. Die explizite Verketten CControlled<'T3> ist erforderlich, wenn die Typargumente nicht abgeleitet werden können.

Der Typ 'T3 ist im Kontext von AllCControlledkonkret, da er für jeden Aufruf von AllCControlledbekannt ist. Das bedeutet, dass der Einstiegspunkt des Programms , der nicht typ parametrisiert werden kann, bekannt ist, also der konkrete Typ 'T3 für jeden Aufruf von AllCControlled, so dass eine geeignete Implementierung für diese bestimmte Typauflösung generiert werden kann. Sobald der Einstiegspunkt zu einem Programm bekannt ist, können alle Verwendungen von Typparametern zur Kompilierungszeit eliminiert werden. Wir bezeichnen diesen Prozess als Monomorphisierung.

Es sind einige Einschränkungen erforderlich, um sicherzustellen, dass dies zur Kompilierungszeit tatsächlich statt nur zur Laufzeit erfolgen kann.

Einschränkungen

Betrachten Sie das folgende Beispiel:

    operation Foo<'TArg> (
        op : 'TArg => Unit,
        arg : 'TArg
    ) : Unit {

        let cbit = RandomInt(2) == 0;
        Foo(CControlled(op), (cbit, arg));        
    } 

Ignoriert, dass ein Aufruf von Foo zu einer Endlosschleife führt, dient sie zum Zweck der Illustration. Foo ruft sich mit der klassisch kontrollierten Version der ursprünglichen Operation op auf, die übergeben wurde, sowie ein Tupel, das zusätzlich zum ursprünglichen Argument ein zufälliges klassisches Bit enthält.

Für jede Iteration in der Rekursion wird der Typparameter 'TArg des nächsten Aufrufs in (Bool, 'TArg)aufgelöst, wobei 'TArg der Typparameter des aktuellen Aufrufs ist. Angenommen, Foo mit dem Vorgang H und einem Argument arg vom Typ Qubitaufgerufen wird. Foo ruft sich dann mit einem Typargument (Bool, Qubit)auf, das dann Foo mit einem Typargument (Bool, (Bool, Qubit))usw. aufruft. In diesem Fall kann Foo zur Kompilierungszeit nicht monomorphisiert werden.

Zusätzliche Einschränkungen gelten für Zyklen im Aufrufdiagramm, die nur typ parametrisierte Aufrufe umfassen. Jeder aufrufbare Aufruf muss nach dem Durchlaufen des Zyklus mit demselben Satz von Typargumenten aufgerufen werden.

Hinweis

Es wäre möglich, weniger restriktiv zu sein und erfordern, dass für jeden aufrufbaren Zyklus eine begrenzte Anzahl von Zyklen vorhanden ist, nach denen er mit dem ursprünglichen Satz von Typargumenten aufgerufen wird, z. B. für die folgende Funktion:

   function Bar<'T1,'T2,'T3>(a1:'T1, a2:'T2, a3:'T3) : Unit{
       Bar<'T2,'T3,'T1>(a2, a3, a1);
   }

Der Einfachheit halber wird die restriktivere Anforderung erzwungen. Beachten Sie, dass für Zyklen, die mindestens einen konkreten Aufruf ohne Typparameter umfassen, durch einen solchen Aufruf sichergestellt wird, dass die typ parametrisierten Aufrufe innerhalb dieses Zyklus immer mit einem festen Satz von Typargumenten aufgerufen werden.