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 => Unit
och 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.