Dichiarazioni chiamabili
Le dichiarazioni chiamabili, o callable, dichiarate in un ambito globale sono visibili pubblicamente per impostazione predefinita; ovvero possono essere usati in qualsiasi punto dello stesso progetto e in un progetto che fa riferimento all'assembly in cui sono dichiarati. I modificatori di accesso consentono di limitarne la visibilità solo all'assembly corrente, in modo che i dettagli di implementazione possano essere modificati in un secondo momento senza interrompere il codice che si basa su una libreria specifica.
Q# supporta due tipi di oggetti chiamabili: operazioni e funzioni. L'argomento Operazioni e funzioni illustra la distinzione tra questi due elementi. Q# supporta anche la definizione di modelli: ad esempio, implementazioni parametrizzate dal tipo per un determinato oggetto chiamabile. Per altre informazioni, vedere Parametrizzazioni di tipi.
Nota
Tali implementazioni parametrizzate dal tipo non possono usare costrutti di linguaggio che si basano su proprietà specifiche degli argomenti di tipo; attualmente non è possibile esprimere vincoli di tipo in Q# o definire implementazioni specializzate per determinati argomenti di tipo.
Chiamabili e funtori
Q# consente implementazioni specializzate per scopi specifici. Ad esempio, le operazioni in Q# possono definire in modo implicito o esplicito il supporto per determinati funtori e insieme alle implementazioni specializzate da richiamare quando un funtore specifico viene applicato a tale oggetto chiamabile.
Un funtore, in un certo senso, è una factory che definisce una nuova implementazione chiamabile che ha una relazione specifica con l'oggetto chiamabile a cui è stata applicata. I funtori hanno una caratteristica in più rispetto alle funzioni tradizionali di livello superiore, in quanto richiedono l'accesso ai dettagli di implementazione dell'oggetto chiamabile a cui sono stati applicati. In tal senso, sono simili ad altre factory, ad esempio i modelli. Possono essere applicati anche a chiamabili con parametri di tipo.
Si consideri l'operazione seguente: ApplyQFT
operation ApplyQFT(qs : Qubit[]) : Unit is Adj + Ctl {
let length = Length(qs);
Fact(length >= 1, "ApplyQFT: Length(qs) must be at least 1.");
for i in length - 1..-1..0 {
H(qs[i]);
for j in 0..i - 1 {
Controlled R1Frac([qs[i]], (1, j + 1, qs[i - j - 1]));
}
}
}
Questa operazione accetta un argomento di tipo Qubit[]
e restituisce un valore di tipo Unit
. L'annotazione is Adj + Ctl
nella dichiarazione di ApplyQFT
indica che l'operazione supporta sia Adjoint
che il funtore Controlled
. Per altre informazioni, vedere Caratteristiche dell'operazione. L'espressione Adjoint ApplyQFT
accede alla specializzazione che implementa l'oggetto adiacente di ApplyQFT
e Controlled ApplyQFT
accede alla specializzazione che implementa la versione controllata di ApplyQFT
.
Oltre all'argomento dell'operazione originale, la versione controllata di un'operazione accetta una matrice di qubit di controllo e applica l'operazione originale sulla condizione in cui tutti questi qubit di controllo si trovano in uno stato |1⟩.
In teoria, un'operazione per cui è possibile definire una versione contigua deve avere anche una versione controllata e viceversa. In pratica, tuttavia, potrebbe essere difficile sviluppare un'implementazione per l'una o l'altra, in particolare per le implementazioni probabilistiche che seguono un modello di ripetizione fino al successo. Per questo motivo, Q# consente di dichiarare il supporto per ogni funtore singolarmente. Tuttavia, poiché i due funtori sono commutati, un'operazione che definisce il supporto per entrambi deve avere anche un'implementazione (in genere definita in modo implicito, ovvero generata dal compilatore) per quando entrambi i funtori vengano applicati all'operazione.
Non esistono funtori che possono essere applicati alle funzioni. Le funzioni attualmente hanno esattamente un'implementazione del corpo e nessun'altra specializzazione. Ad esempio, la dichiarazione
function Hello (name : String) : String {
$"Hello, {name}!"
}
equivale a
function Hello (name : String) : String {
body ... {
$"Hello, {name}!"
}
}
In questo caso, body
indica che l'implementazione specificata si applica al corpo predefinito della funzione Hello
, vale a dire che l'implementazione viene richiamata quando non sono stati applicati funtori o altri meccanismi factory prima della chiamata. I tre punti in body ...
corrispondono a una direttiva del compilatore che indica che gli elementi dell'argomento nella dichiarazione di funzione devono essere copiati e incollati in questo punto.
I motivi alla base dell'indicazione esplicita della posizione in cui gli argomenti della dichiarazione chiamabile padre devono essere copiati e incollati sono due: uno, non è necessario ripetere la dichiarazione dell'argomento e due, garantisce che i funtori che richiedono argomenti aggiuntivi, ad esempio il Controlled
functor, possano essere introdotti in modo coerente.
Quando è presente esattamente una specializzazione che definisce l'implementazione del corpo predefinito, è possibile omettere il wrapping aggiuntivo del modulo body ... { <implementation> }
.
Ricorsione
Gli oggetti chiamabili in Q# possono essere direttamente o indirettamente ricorsivi e possono essere dichiarati in qualsiasi ordine. Un'operazione o una funzione può chiamare se stessa oppure un altro oggetto chiamabile che chiama direttamente o indirettamente il chiamante.
Quando viene eseguito su hardware quantistico, lo spazio dello stack può essere limitato e le ricorsioni che superano tale limite di spazio dello stack comportano un errore di runtime.