Sdílet prostřednictvím


Parametrizace typů

Q# podporuje parametrizované operace a funkce typu. Standardní knihovny Q# využívají typově parametrizované volatelné k zajištění řadu užitečných abstrakcí, včetně funkcí, jako jsou Mapped a Fold, které jsou známé z funkčních jazyků.

Abychom motivovali koncept parametrizací typů, zvažte příklad funkce Mapped, která aplikuje danou funkci na každou hodnotu v matici a vrací novou matici s vypočítanými hodnotami. Tuto funkci lze dokonale popsat bez zadání typů položek vstupních a výstupních polí. Vzhledem k tomu, že přesné typy nemění implementaci funkce Mapped, je vhodné definovat tuto implementaci pro libovolné typy položek; Chceme definovat továrnu nebo šablonu, že vzhledem k konkrétním typům položek ve vstupním a výstupním poli vrátí odpovídající implementaci funkce. Tento pojem je formalizován ve formě parametrů typu.

Konkretizace

Jakákoli operace nebo deklarace funkce může určovat jeden nebo více parametrů typu, které lze použít jako typy nebo část typů vstupu nebo výstupu volatelného volání, nebo obojího. Výjimky jsou vstupní body, které musí být konkrétní a nelze je parametrizovat. Názvy parametrů typu začínají značkou (') a můžou se ve vstupních a výstupních typech zobrazovat několikrát. Všechny argumenty, které odpovídají stejnému parametru typu v volatelném podpisu, musí být stejného typu.

Typově parametrizovaný volatelný musí být zřetězen, to znamená, že musí být zadán s potřebnými argumenty typu, aby bylo možné přiřadit nebo předat jako argument, aby všechny parametry typu mohly být nahrazeny konkrétními typy. Typ je považován za konkrétní, pokud se jedná o jeden z předdefinovaných typů, typ struct nebo pokud je konkrétní v aktuálním rozsahu. Následující příklad ukazuje, co znamená, že typ má být konkrétní v aktuálním rozsahu, a je vysvětlen podrobněji níže:

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

Funkce CControlled je definována v oboru názvů Microsoft.Quantum.Canon. Vezme operaci op typu 'TIn => Unit jako argument a vrátí novou operaci typu (Bool, 'TIn) => Unit, která použije původní operaci, za předpokladu, že klasický bit (typ Bool) je nastaven na true; to se často označuje jako klasicky řízená verze op.

Funkce Mapped přebírá pole libovolného typu položky 'T1 jako argument, použije danou funkci mapper pro každou položku a vrátí novou matici typu 'T2[] obsahující mapované položky. Definuje se v oboru názvů Microsoft.Quantum.Array. Pro účely příkladu jsou parametry typu očíslovány, aby se zabránilo matoucí diskuzi tím, že zadáte parametry typu v obou funkcích se stejným názvem. To není nutné; Parametry typu pro různé volatelné můžou mít stejný název a zvolený název je viditelný a relevantní pouze v definici tohoto volatelného volání.

Funkce AllCControlled přebírá pole operací a vrací nové pole obsahující klasicky řízené verze těchto operací. Volání Mapped přeloží parametr typu 'T1 na 'T3 => Unita jeho typ parametru 'T2(Bool,'T3) => Unit. Argumenty typu překladu jsou odvozeny kompilátorem na základě typu daného argumentu. Říkáme, že jsou implicitně definované argumentem výrazu volání. Argumenty typu lze také zadat explicitně, jak je to provedeno pro CControlled na stejném řádku. Explicitní konkretizace CControlled<'T3> je nutná, pokud argumenty typu nelze odvodit.

Typ 'T3 je konkrétní v kontextu AllCControlled, protože je známý pro každé vyvoláníAllCControlled. To znamená, že jakmile je vstupní bod programu - který nelze typ-parametrizovat - je známo, tak je konkrétní typ 'T3 pro každé volání AllCControlled, aby bylo možné vygenerovat vhodné implementace pro toto řešení typu. Jakmile je vstupní bod programu znám, lze při kompilaci eliminovat všechna použití parametrů typu. Tento proces označujeme jako monomorfizace.

Některá omezení jsou nutná k zajištění toho, aby se to skutečně provádělo v době kompilace, nikoli v době běhu.

Omezení

Podívejte se na následující příklad:

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

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

Ignorování vyvolání Foo vede k nekonečné smyčce, slouží pro účely ilustrace. Foo se vyvolá s klasicky řízenou verzí původní operace op, která byla předána, a také řazená kolekce členů obsahující náhodný klasický bit kromě původního argumentu.

Pro každou iteraci v rekurzi se přeloží parametr typu 'TArg dalšího volání na (Bool, 'TArg), kde 'TArg je parametr typu aktuálního volání. Konkrétně předpokládejme, že se Foo vyvolá pomocí H operace a argumentu arg typu Qubit. Foo se pak vyvolá pomocí argumentu typu (Bool, Qubit), který pak vyvolá Foo s argumentem typu (Bool, (Bool, Qubit))atd. V tomto případě Foo nelze v době kompilace monomorfovat.

Další omezení platí pro cykly v grafu volání, která zahrnují pouze typově parametrizované volatelné. Po procházení cyklu je potřeba vyvolat každou volání se stejnou sadou argumentů typu.

Poznámka:

Bylo by možné být méně omezující a vyžadovat, aby pro každý volatelný v cyklu byl konečný počet cyklů, po kterých je vyvolán s původní sadou argumentů typu, jako je například případ pro následující funkci:

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

Kvůli jednoduchosti se vynucuje přísnější požadavek. Všimněte si, že pro cykly, které zahrnují alespoň jeden konkrétní volatelný bez jakéhokoli parametru typu, takový volatelný zajišťuje, že typově parametrizované volatelné volatelné v daném cyklu jsou vždy volány s pevnou sadou argumentů typu.