Sdílet prostřednictvím


Parametrizace typů

Q# podporuje typ parametrizované operace a funkce. Standardní Q# knihovny velmi využívají typ-parametrizované volatables k poskytování řadu užitečných abstrakcí, včetně funkcí jako Mapped a Fold , které jsou známé z funkčních jazyků.

Pokud chcete motivovat koncept parametrizace typů, podívejte se na příklad funkce Mapped, která použije danou funkci na každou hodnotu v poli a vrátí nové pole 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, dává smysl, že by mělo být možné definovat tuto implementaci pro typy libovolných položek. Chceme definovat továrnu nebo šablonu , která vzhledem k konkrétním typům pro položky 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čit jeden nebo více parametrů typu, které lze použít jako typy nebo část typů, vstupu nebo výstupu volatelného vstupu nebo výstupu, případně obojího. Výjimkou jsou vstupní body, které musí být konkrétní a nelze je typově parametrizovat. Názvy parametrů typu začínají zaškrtnutím (') a ve vstupním a výstupním typu se můžou objevit několikrát. Všechny argumenty, které odpovídají stejnému parametru typu v volatelném podpisu, musí být stejného typu.

Parametrizovaný volatelný typ musí být konkretizován, to znamená, že musí být opatřen potřebnými argumenty typu, aby bylo možné jej přiřadit nebo předat jako argument, aby všechny parametry typu mohly být nahrazeny konkrétními typy. Typ se považuje za beton, pokud je to jeden z předdefinovaných typů, typ definovaný uživatelem nebo pokud je beton v rámci aktuálního rozsahu. Následující příklad ukazuje, co znamená, že typ má být konkrétní v rámci aktuálního oboru, a je podrobněji vysvětlen níže:

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

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

Funkce Mapped přebírá pole libovolného typu 'T1 položky jako argument, použije danou mapper funkci na každou položku a vrátí nové pole typu 'T2[] obsahující namapované položky. Je definován v Microsoft.Quantum.Array oboru názvů. Pro účely tohoto příkladu jsou parametry typu očíslovány, aby diskuze byla matoucí, protože parametry typu v obou funkcích mají stejný název. 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 rámci definice tohoto volatelného.

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 'T1 typu na 'T3 => Unita jeho parametr 'T2 typu na (Bool,'T3) => Unit. Argumenty typu překladu jsou odvozeny kompilátorem na základě typu daného argumentu. Říkáme, že jsou implicitně definovány argumentem výrazu volání. Argumenty typu lze také zadat explicitně, jak je tomu u CControlled stejného řá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 znám vstupní bod programu - který nelze typ-parametrizovat - tak je konkrétní typ 'T3 pro každé volání AllCControlled, aby bylo možné vygenerovat vhodnou implementaci pro tento konkrétní typ rozlišení. Jakmile je vstupní bod do programu známý, lze při kompilaci eliminovat všechna použití parametrů typu. Tento proces označujeme jako monomorfizaci.

Některá omezení jsou nutná, aby se zajistilo, že to skutečně lze provést v době kompilace, nikoli pouze za běhu.

Omezení

Uvažujte následující příklad:

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

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

Když ignorujeme, že vyvolání Foo bude mít za následek nekonečnou smyčku, slouží pro ilustraci. Foo vyvolá sám sebe s klasicky řízenou verzí původní operace op , která byla předána, stejně jako řazená kolekce členů obsahující náhodný klasický bit kromě původního argumentu.

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

Další omezení platí pro cykly v grafu volání, které zahrnují pouze typově parametrizované volatelné položky. Každé volatelné volání musí být vyvoláno se stejnou sadou argumentů typu po procházení cyklu.

Poznámka

Bylo by možné být méně omezující a vyžadovat, aby pro každý volatelný v cyklu existoval konečný počet cyklů, po kterém se vyvolá s původní sadou argumentů typu, 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);
   }

Pro zjednodušení se vynucuje restriktivnější požadavek. Všimněte si, že u cyklů, které zahrnují alespoň jeden konkrétní volatelný bez parametru typu, takové volatelné zajistí, že typově parametrizované volání v daném cyklu budou vždy volány s pevnou sadou argumentů typu.