Condividi tramite


Operazioni e funzioni

Come elaborato in modo più dettagliato nella descrizione del tipo di dati qubit, i calcoli quantistici vengono eseguiti sotto forma di effetti collaterali delle operazioni supportate in modo nativo nel processore quantistico di destinazione. Questi sono infatti gli unici effetti collaterali in Q#. Poiché tutti i tipi non sono modificabili, non sono presenti effetti collaterali che influiscono su un valore rappresentato in modo esplicito in Q#. Pertanto, purché un'implementazione di un determinato chiamabile non chiami direttamente o indirettamente una di queste operazioni implementate in modo nativo, l'esecuzione produce sempre lo stesso output, dato lo stesso input.

Q# consente di suddividere in modo esplicito tali calcoli puramente deterministici in funzioni. Poiché il set di istruzioni supportate in modo nativo non è fisso e integrato nel linguaggio stesso, ma piuttosto completamente configurabile ed espresso come Q# libreria, determinism è garantito richiedendo che le funzioni possano chiamare solo altre funzioni e non possono chiamare alcuna operazione. Inoltre, le istruzioni native che non sono deterministiche, ovvero perché influiscono sullo stato quantistico, vengono rappresentate come operazioni. Con queste due restrizioni, le funzioni possono essere valutate non appena il valore di input è noto e, in linea di principio, non è mai necessario valutare più volte per lo stesso input.

Q# di conseguenza distingue tra due tipi di chiamate: operazioni e funzioni. Tutti i chiamabili accettano un singolo argomento (potenzialmente tuple-valued) come input e producono un singolo valore (tuple) come output. Dal punto di vista della sintassi, il tipo di operazione è espresso come <TIn> => <TOut> is <Char>, dove <TIn> deve essere sostituito dal tipo di argomento, <TOut> deve essere sostituito dal tipo restituito e <Char> deve essere sostituito dalle caratteristiche dell'operazione. Se non è necessario specificare alcuna caratteristica, la sintassi viene semplificata in <TIn> => <TOut>. Analogamente, i tipi di funzione vengono espressi come <TIn> -> <TOut>.

Oltre a questa garanzia determinismo, c'è poca differenza tra operazioni e funzioni. Entrambi sono valori di prima classe che possono essere passati liberamente; possono essere usati come valori o argomenti restituiti ad altri chiamabili, come illustrato nell'esempio seguente:

function Pow<'T>(op : 'T => Unit, pow : Int) : 'T => Unit {
    return PowImpl(op, pow, _);
}

Entrambi possono essere creata un'istanza basata su una definizione parametrizzata di tipo , ad esempio la funzione Pow parametrizzata di tipo precedente e possono essere parzialmente applicate come fatto nell'istruzione nell'esempio return .

Caratteristiche dell'operazione

Oltre alle informazioni sul tipo di input e di output, il tipo di operazione contiene informazioni sulle caratteristiche di un'operazione. Queste informazioni, ad esempio, descrivono quali functor sono supportati dall'operazione. Inoltre, la rappresentazione interna contiene anche informazioni rilevanti per l'ottimizzazione che viene dedotto dal compilatore.

Le caratteristiche di un'operazione sono un set di etichette predefinite e incorporate. Sono espresse sotto forma di un'espressione speciale che fa parte della firma del tipo. L'espressione è costituita da uno dei set predefiniti di etichette o da una combinazione di espressioni di caratteristiche tramite un operatore binario supportato.

Esistono due set predefiniti, ovvero Adj e Ctl.

  • Adj è il set che contiene una singola etichetta che indica che un'operazione è adiacente, ovvero supporta il Adjoint functor e la trasformazione quantistica applicata può essere "annullata", ovvero può essere invertita.
  • Ctl è il set che contiene un'unica etichetta che indica che un'operazione è controllabile, ovvero supporta il Controlled functor e la relativa esecuzione può essere condizionale sullo stato di altri qubit.

I due operatori supportati come parte delle espressioni di caratteristiche sono l'unione dei set + e l'intersezione dei set *. In EBNF:

    predefined = "Adj" | "Ctl";
    characteristics = predefined 
        | "(", characteristics, ")" 
        | characteristics ("+"|"*") characteristics;

Come ci si aspetterebbe, * ha maggiore precedenza di + ed entrambi sono associativi a sinistra. Il tipo di un'operazione unitaria, ad esempio, viene espresso come <TIn> => <TOut> is Adj + Ctl, dove <TIn> deve essere sostituito con il tipo dell'argomento dell'operazione e <TOut> sostituito con il tipo del valore restituito.

Nota

L'indicazione delle caratteristiche di un'operazione in questo formato presenta due vantaggi principali. Il primo è che è possibile introdurre nuove etichette senza aggiungere in modo esponenziale molte parole chiave del linguaggio per tutte le combinazioni di etichette. Forse più importante, l'uso delle espressioni per indicare le caratteristiche di un'operazione supporta anche le parametriizzazioni sulle caratteristiche dell'operazione in futuro.