Condividi tramite


Parametrizzazioni di tipi

Q# supporta operazioni e funzioni con parametri di tipo. Le librerie standard Q# usano pesantemente i callable con parametri di tipo per fornire una serie di astrazioni utili, tra cui funzioni come Mapped e Fold familiari da linguaggi funzionali.

Per motivare il concetto di parametrizzazione del tipo, si consideri l'esempio della funzione Mapped, che applica una determinata funzione a ogni valore in una matrice e restituisce una nuova matrice con i valori calcolati. Questa funzionalità può essere perfettamente descritta senza specificare i tipi di elemento delle matrici di input e output. Poiché i tipi esatti non modificano l'implementazione della funzione Mapped, è opportuno definire questa implementazione per i tipi di elementi arbitrari; Si vuole definire un modello di factory o che, dato i tipi concreti per gli elementi nella matrice di input e output, restituisce l'implementazione della funzione corrispondente. Questa nozione è formalizzata sotto forma di parametri di tipo.

Concretizzazione

Qualsiasi dichiarazione di operazione o funzione può specificare uno o più parametri di tipo che possono essere usati come tipi, o parte dei tipi, dell'input o dell'output chiamabile o entrambi. Le eccezioni sono punti di ingresso, che devono essere concreti e non possono essere parametrizzati dal tipo. I nomi dei parametri di tipo iniziano con un segno di graduazione (') e possono essere visualizzati più volte nei tipi di input e output. Tutti gli argomenti che corrispondono allo stesso parametro di tipo nella firma chiamabile devono essere dello stesso tipo.

Prima di poter essere assegnato o passato come argomento, è necessario che tutti i parametri di tipo possano essere sostituiti con tipi concreti. Un tipo viene considerato concreto se è uno dei tipi predefiniti, un tipo struct o se è concreto all'interno dell'ambito corrente. L'esempio seguente illustra cosa significa che un tipo sia concreto nell'ambito corrente ed è illustrato in modo più dettagliato di seguito:

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

La funzione CControlled viene definita nello spazio dei nomi Microsoft.Quantum.Canon. Accetta un'operazione op di tipo 'TIn => Unit come argomento e restituisce una nuova operazione di tipo (Bool, 'TIn) => Unit che applica l'operazione originale, a condizione che un bit classico (di tipo Bool) sia impostato su true; viene spesso definita versione controllata classica di op.

La funzione Mapped accetta una matrice di un tipo di elemento arbitrario 'T1 come argomento, applica la funzione mapper specificata a ogni elemento e restituisce una nuova matrice di tipo 'T2[] contenente gli elementi mappati. Viene definito nello spazio dei nomi Microsoft.Quantum.Array. Ai fini dell'esempio, i parametri di tipo vengono numerati per evitare di creare confusione nella discussione assegnando i parametri di tipo in entrambe le funzioni con lo stesso nome. Questo non è necessario; I parametri di tipo per diversi chiamabili possono avere lo stesso nome e il nome scelto è visibile e pertinente solo all'interno della definizione di tale chiamabile.

La funzione AllCControlled accetta una matrice di operazioni e restituisce una nuova matrice contenente le versioni classicamente controllate di queste operazioni. La chiamata di Mapped risolve il relativo parametro di tipo 'T1 in 'T3 => Unite il relativo parametro di tipo 'T2 in (Bool,'T3) => Unit. Gli argomenti di tipo di risoluzione vengono dedotti dal compilatore in base al tipo dell'argomento specificato. Si supponga che siano in modo implicito definiti dall'argomento dell'espressione di chiamata. Gli argomenti di tipo possono anche essere specificati in modo esplicito, come viene fatto per CControlled nella stessa riga. La concretizzazione esplicita CControlled<'T3> è necessaria quando non è possibile dedurre gli argomenti di tipo.

Il tipo 'T3 è concreto all'interno del contesto di AllCControlled, poiché è noto per ogni chiamata di AllCControlled. Ciò significa che non appena il punto di ingresso del programma , che non può essere parametrizzato dal tipo, è noto, quindi è il tipo concreto 'T3 per ogni chiamata a AllCControlled, in modo che sia possibile generare un'implementazione adatta per la risoluzione di quel particolare tipo. Quando il punto di ingresso a un programma è noto, tutti gli utilizzi dei parametri di tipo possono essere eliminati in fase di compilazione. Si fa riferimento a questo processo come monomorfizzazione.

Alcune restrizioni sono necessarie per garantire che questa operazione possa essere effettivamente eseguita in fase di compilazione anziché solo in fase di esecuzione.

Restrizioni

Si consideri l'esempio seguente:

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

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

Ignorando che una chiamata di Foo comporta un ciclo infinito, serve allo scopo dell'illustrazione. Foo richiama se stesso con la versione classica controllata dell'operazione originale op passata, nonché una tupla contenente un bit classico casuale oltre all'argomento originale.

Per ogni iterazione nella ricorsione, il parametro di tipo 'TArg della chiamata successiva viene risolto in (Bool, 'TArg), dove 'TArg è il parametro di tipo della chiamata corrente. In concreto, si supponga che Foo venga richiamato con l'operazione H e un argomento arg di tipo Qubit. Foo quindi richiama se stesso con un argomento di tipo (Bool, Qubit), che quindi richiama Foo con un argomento di tipo (Bool, (Bool, Qubit))e così via. Chiaramente, in questo caso Foo non può essere monomorfizzato in fase di compilazione.

Le restrizioni aggiuntive si applicano ai cicli nel grafico delle chiamate che coinvolgono solo callable con parametri di tipo. Ogni oggetto chiamabile deve essere richiamato con lo stesso set di argomenti di tipo dopo l'attraversamento del ciclo.

Nota

Sarebbe possibile essere meno restrittivo e richiedere che per ogni chiamabile nel ciclo esista un numero finito di cicli dopo il quale viene richiamato con il set originale di argomenti di tipo, ad esempio il caso per la funzione seguente:

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

Per semplicità, viene applicato il requisito più restrittivo. Si noti che per i cicli che coinvolgono almeno un callable concreto senza alcun parametro di tipo, tale callable garantisce che i callable con parametri di tipo all'interno di tale ciclo vengano sempre chiamati con un set fisso di argomenti di tipo.