Chiusure

Le chiusura sono chiamabili che acquisiscono variabili dall'ambiente contenitore. È possibile creare sia le chiusura di funzione che di operazione. Una chiusura dell'operazione può essere creata all'interno di una funzione, ma può essere applicata solo in un'operazione.

Q# include due meccanismi per la creazione di chiusura: espressioni lambda e applicazione parziale.

Espressioni lambda

Un'espressione lambda crea una funzione o un'operazione anonima. La sintassi di base è una tupla di simboli per associare i parametri, una freccia (-> per una funzione e => per un'operazione) e un'espressione da valutare quando applicata.

// Function that captures 'x':
y -> x + y

// Operation that captures 'qubit':
deg => Rx(deg * PI() / 180.0, qubit)

// Function that captures nothing:
(x, y) -> x + y

Parametri

I parametri vengono associati usando una tupla di simboli identica al lato sinistro di un'istruzione di dichiarazione di variabile. Il tipo della tupla del parametro è implicito. Le annotazioni dei tipi non sono supportate; se l'inferenza del tipo ha esito negativo, potrebbe essere necessario creare una dichiarazione chiamabile di primo livello e usare invece un'applicazione parziale.

Variabili di acquisizione modificabili

Non è possibile acquisire variabili modificabili. Se è sufficiente acquisire il valore di una variabile modificabile all'istante in cui viene creata l'espressione lambda, è possibile creare una copia non modificabile:

// ERROR: 'variable' cannot be captured.
mutable variable = 1;
let f = () -> variable;

// OK.
let value = variable;
let g = () -> value;

Caratteristiche

Le caratteristiche di un'operazione anonima vengono dedotte in base alle applicazioni dell'espressione lambda. Se l'espressione lambda viene usata con un'applicazione functor o in un contesto che prevede una caratteristica, l'espressione lambda viene quindi dedotta per avere tale caratteristica. Ad esempio:

operation NoOp(q : Qubit) : Unit is Adj {}
operation Main() : Unit {
    use q = Qubit();
    let foo = () => NoOp(q);
    foo(); // Has type Unit => Unit with no characteristics

    let bar = () => NoOp(q);
    Adjoint bar(); // Has type Unit => Unit is Adj
}

Se sono necessarie caratteristiche diverse per un'operazione lambda rispetto a quella dedotta, sarà invece necessario creare una dichiarazione di operazione di primo livello.

Applicazione parziale

L'applicazione parziale è una pratica abbreviata per l'applicazione di alcuni argomenti, ma non tutti, di argomenti chiamabili. La sintassi è la stessa di un'espressione di chiamata, ma gli argomenti non validi vengono sostituiti con _. Concettualmente, l'applicazione parziale equivale a un'espressione lambda che acquisisce gli argomenti applicati e accetta gli argomenti non applicati come parametri.

Ad esempio, dato che f è una funzione ed o è un'operazione e la variabile x acquisita non è modificabile:

Applicazione parziale Espressioni lambda
f(x, _) a -> f(x, a)
o(x, _) a => o(x, a)
f(_, (1, _)) (a, b) -> f(a, (1, b))[^1]
f((_, _, x), (1, _)) ((a, b), c) -> f((a, b, x), (1, c))

Variabili di acquisizione modificabili

A differenza delle espressioni lambda, l'applicazione parziale può acquisire automaticamente una copia del valore di una variabile modificabile:

mutable variable = 1;
let f = Foo(variable, _);

Equivale all'espressione lambda seguente:

mutable variable = 1;
let value = variable;
let f = x -> Foo(value, x);

[^1]: la tupla dei parametri è strettamente scritta (a, (b)), ma (b) equivale a b.