Dela via


Typparameterisering

Q# stöder typparameteriserade åtgärder och funktioner. Standardbiblioteken Q# använder sig av typparmetriserade anropbara objekt för att tillhandahålla en mängd användbara abstraktioner, inklusive funktioner som Mapped och Fold som är bekanta från funktionella språk.

För att motivera begreppet typparameteriseringar bör du överväga exemplet med funktionen Mapped, som tillämpar en viss funktion på varje värde i en matris och returnerar en ny matris med de beräknade värdena. Den här funktionen kan beskrivas perfekt utan att ange objekttyperna för indata- och utdatamatriserna. Eftersom de exakta typerna inte ändrar implementeringen av funktionen Mappedär det klokt att definiera den här implementeringen för godtyckliga objekttyper. Vi vill definiera en fabrik eller mall som, med tanke på de konkreta typerna för objekten i indata- och utdatamatrisen, returnerar motsvarande funktionsimplementering. Det här begreppet formaliseras i form av typparametrar.

Sammanfogning

En åtgärd eller funktionsdeklaration kan ange en eller flera typparametrar som kan användas som typer, eller en del av typerna, av den anropbaras indata eller utdata, eller båda. Undantagen är startpunkter, som måste vara konkreta och inte kan typparametriseras. Typparameternamn börjar med en bock (') och kan visas flera gånger i indata- och utdatatyperna. Alla argument som motsvarar samma typparameter i den anropbara signaturen måste vara av samma typ.

En typparmetriserad anropsbar måste sammanfogas, det vill säga den måste anges med de nödvändiga typargumenten innan den kan tilldelas eller skickas som argument, så att alla typparametrar kan ersättas med konkreta typer. En typ anses vara konkret om den är en av de inbyggda typerna, en användardefinierad typ eller om den är konkret inom det aktuella omfånget. I följande exempel visas vad det innebär att en typ ska vara konkret inom det aktuella omfånget och förklaras mer detaljerat nedan:

    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); 
    }

Funktionen CControlled definieras i Microsoft.Quantum.Canon namnområdet. Den tar en åtgärd op av typen 'TIn => Unit som argument och returnerar en ny åtgärd av typen (Bool, 'TIn) => Unit som tillämpar den ursprungliga åtgärden, förutsatt att en klassisk bit (av typen Bool) är inställd på true. Detta kallas ofta för den klassiskt kontrollerade versionen av op.

Funktionen Mapped tar en matris av en godtycklig objekttyp 'T1 som argument, tillämpar den angivna mapper funktionen på varje objekt och returnerar en ny matris av typen 'T2[] som innehåller de mappade objekten. Den definieras i Microsoft.Quantum.Array namnområdet. I exemplet är typparametrarna numrerade för att undvika att göra diskussionen mer förvirrande genom att ge typparametrarna i båda funktionerna samma namn. Detta är inte nödvändigt. typparametrar för olika anropbara objekt kan ha samma namn och det valda namnet är bara synligt och relevant inom definitionen av det anropningsbara namnet.

Funktionen AllCControlled tar en matris med åtgärder och returnerar en ny matris som innehåller de klassiskt kontrollerade versionerna av dessa åtgärder. Anropet för Mapped löser dess typparameter 'T1 till 'T3 => Unitoch dess typparameter 'T2 till (Bool,'T3) => Unit. Matchningstypargumenten härleds av kompilatorn baserat på typen av det angivna argumentet. Vi säger att de definieras implicit av argumentet för anropsuttrycket. Typargument kan också anges uttryckligen, vilket görs för CControlled på samma rad. Den explicita sammanfogningen CControlled<'T3> är nödvändig när typargumenten inte kan härledas.

Typen 'T3 är konkret inom kontexten AllCControlled, eftersom den är känd för varje anrop av AllCControlled. Det innebär att så snart programmets startpunkt - som inte kan vara typparametriserad - är känd, så är den konkreta typen 'T3 för varje anrop till AllCControlled, så att en lämplig implementering för den specifika typupplösningen kan genereras. När startpunkten för ett program är känd kan all användning av typparametrar elimineras vid kompilering. Vi kallar den här processen monomorfisering.

Vissa begränsningar krävs för att säkerställa att detta verkligen kan göras vid kompilering i stället för endast vid körning.

Begränsningar

Se följande exempel:

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

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

Om du ignorerar att ett anrop av Foo resulterar i en oändlig loop fungerar det för illustrationens syfte. Foo anropar sig själv med den klassiskt kontrollerade versionen av den ursprungliga åtgärden op som har skickats, samt en tuppeln som innehåller en slumpmässig klassisk bit utöver det ursprungliga argumentet.

För varje iteration i rekursionen matchas typparametern 'TArg för nästa anrop till (Bool, 'TArg), där 'TArg är typparametern för det aktuella anropet. Anta konkret att Foo anropas med åtgärden H och ett argument arg av typen Qubit. Foo anropar sig sedan med ett typargument (Bool, Qubit), som sedan anropar Foo med ett typargument (Bool, (Bool, Qubit))och så vidare. Det är uppenbart att i det här fallet Foo inte kan monomorfiseras vid kompilering.

Ytterligare begränsningar gäller för cykler i anropsdiagrammet som endast omfattar typparmetriserade anropsbara objekt. Varje anropningsbar måste anropas med samma uppsättning typargument efter att cykeln har bläddrats igenom.

Anteckning

Det skulle vara möjligt att vara mindre restriktiv och kräva att det för varje anropbar i cykeln finns ett begränsat antal cykler varefter det anropas med den ursprungliga uppsättningen typargument, till exempel fallet för följande funktion:

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

För enkelhetens skull tillämpas det mer restriktiva kravet. Observera att för cykler som omfattar minst en konkret anropningsbar utan någon typparameter, säkerställer en sådan anropningsbar att de typparametriserade anropsbara objekten i cykeln alltid anropas med en fast uppsättning typargument.