Delen via


Type parameteriseringen

Q# ondersteunt bewerkingen en functies met typeparameter. De Q# standaardbibliotheken maken intensief gebruik van type-geparametriseerde aanroepbare aanroepfuncties om een groot aantal nuttige abstracties te bieden, waaronder functies zoals Mapped en Fold die bekend zijn uit functionele talen.

Als u het concept van typeparametrisaties wilt motiveren, bekijkt u het voorbeeld van de functie Mapped, die een bepaalde functie toepast op elke waarde in een matrix en een nieuwe matrix retourneert met de berekende waarden. Deze functionaliteit kan perfect worden beschreven zonder de itemtypen van de invoer- en uitvoermatrices op te geven. Aangezien de exacte typen de implementatie van de functie Mappedniet wijzigen, is het logisch dat deze implementatie kan worden gedefinieerd voor willekeurige itemtypen. We willen een factory of sjabloon definiëren die, gezien de concrete typen voor de items in de invoer- en uitvoermatrix, de bijbehorende functie-implementatie retourneert. Dit idee wordt geformaliseerd in de vorm van typeparameters.

Concretisatie

Elke bewerking of functiedeclaratie kan een of meer typeparameters opgeven die kunnen worden gebruikt als de typen, of een deel van de typen, van de invoer of uitvoer van het aanroepbare, of beide. De uitzonderingen zijn toegangspunten, die beton moeten zijn en niet kunnen worden geparametriseerd. Typeparameternamen beginnen met een vinkje (') en kunnen meerdere keren worden weergegeven in de invoer- en uitvoertypen. Alle argumenten die overeenkomen met dezelfde typeparameter in de aanroepbare handtekening moeten van hetzelfde type zijn.

Een type-geparametriseerde aanroepbare moet worden geconcretiseerd, dat wil gezegd dat het moet worden voorzien van de benodigde typeargumenten voordat deze kan worden toegewezen of doorgegeven als argument, zodat alle typeparameters kunnen worden vervangen door concrete typen. Een type wordt beschouwd als beton als het een van de ingebouwde typen is, een door de gebruiker gedefinieerd type of als het beton binnen het huidige bereik is. In het volgende voorbeeld ziet u wat het betekent dat een type concreet is binnen het huidige bereik. Hieronder wordt meer in detail uitgelegd:

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

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

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

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

De functie CControlled wordt gedefinieerd in de Microsoft.Quantum.Canon naamruimte. Het gebruikt een bewerking op van het type 'TIn => Unit als argument en retourneert een nieuwe bewerking van het type (Bool, 'TIn) => Unit die de oorspronkelijke bewerking toepast, mits een klassieke bit (van het type Bool) is ingesteld op true. Dit wordt vaak de klassiek beheerde versie van opgenoemd.

De functie Mapped gebruikt een matrix van een willekeurig itemtype 'T1 als argument, past de opgegeven mapper functie toe op elk item en retourneert een nieuwe matrix van het type 'T2[] die de toegewezen items bevat. Deze wordt gedefinieerd in de Microsoft.Quantum.Array naamruimte. Voor het doel van het voorbeeld worden de typeparameters genummerd om te voorkomen dat de discussie verwarrender wordt door de typeparameters in beide functies dezelfde naam te geven. Dit is niet nodig; typeparameters voor verschillende aanroepbare functies kunnen dezelfde naam hebben en de gekozen naam is alleen zichtbaar en relevant binnen de definitie van dat aanroepbare.

De functie AllCControlled neemt een matrix van bewerkingen en retourneert een nieuwe matrix met de klassiek beheerde versies van deze bewerkingen. Met de aanroep van Mapped wordt de typeparameter 'T1 omgezet in 'T3 => Unit, en de parameter 'T2 van het type naar (Bool,'T3) => Unit. De argumenten voor het omzetten van het type worden afgeleid door de compiler op basis van het type van het opgegeven argument. We zeggen dat ze impliciet worden gedefinieerd door het argument van de aanroepexpressie. Typeargumenten kunnen ook expliciet worden opgegeven, zoals in dezelfde regel wordt gedaan CControlled . De expliciete concretisatie CControlled<'T3> is nodig wanneer de typeargumenten niet kunnen worden afgeleid.

Het type 'T3 is concreet binnen de context van AllCControlled, omdat het bekend is voor elke aanroep van AllCControlled. Dat betekent dat zodra het ingangspunt van het programma - dat niet kan worden geparametriseerd - bekend is, net als het concrete type 'T3 voor elke aanroep naar AllCControlled, zodat een geschikte implementatie voor dat specifieke type resolutie kan worden gegenereerd. Zodra het beginpunt van een programma bekend is, kunnen alle gebruik van type parameters tijdens het compileren worden geëlimineerd. We noemen dit proces monomorfisatie.

Er zijn enkele beperkingen nodig om ervoor te zorgen dat dit inderdaad kan worden gedaan tijdens het compileren in plaats van alleen tijdens runtime.

Beperkingen

Kijk eens naar het volgende voorbeeld:

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

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

Negerend dat een aanroep van Foo zal resulteren in een oneindige lus, dient het ter illustratie. Foo roept zichzelf aan met de klassiek beheerde versie van de oorspronkelijke bewerking op die is doorgegeven, evenals een tuple met een willekeurige klassieke bit naast het oorspronkelijke argument.

Voor elke iteratie in de recursie wordt het typeparameter 'TArg van de volgende aanroep omgezet in (Bool, 'TArg), waarbij 'TArg de typeparameter van de huidige aanroep is. Stel concreet dat Foo wordt aangeroepen met de bewerking H en een argument arg van het type Qubit. Foo roept zichzelf vervolgens aan met een typeargument (Bool, Qubit), dat vervolgens wordt aangeroepen Foo met een typeargument (Bool, (Bool, Qubit)), enzovoort. Het is duidelijk dat in dit geval Foo niet kan worden monomorfiseerd tijdens het compileren.

Aanvullende beperkingen zijn van toepassing op cycli in de aanroepgrafiek die alleen betrekking hebben op type-geparametriseerde aanroepbare apparaten. Elke aanroepbare moet worden aangeroepen met dezelfde set typeargumenten nadat de cyclus is doorlopen.

Notitie

Het zou mogelijk zijn om minder beperkend te zijn en te vereisen dat voor elke aanroepbare in de cyclus een eindig aantal cycli is waarna deze wordt aangeroepen met de oorspronkelijke set typeargumenten, zoals het geval is voor de volgende functie:

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

Voor het gemak wordt de meer beperkende vereiste afgedwongen. Houd er rekening mee dat voor cycli die betrekking hebben op ten minste één concrete aanroepbare zonder typeparameter, een dergelijke aanroepbare ervoor zorgt dat de type-geparametriseerde aanroepbare aanroepbare elementen binnen die cyclus altijd worden aangeroepen met een vaste set typeargumenten.