Condividi tramite


Sottotipizzazione e varianza

Q# supporta solo alcuni meccanismi di conversione. Le conversioni implicite possono verificarsi solo quando si applicano operatori binari, si valutano espressioni condizionali o si costruisce un valore letterale di matrice. In questi casi, viene determinato un supertipo comune e le conversioni necessarie vengono eseguite automaticamente. A parte queste conversioni implicite, le conversioni esplicite tramite chiamate di funzione sono possibili e spesso necessarie.

Attualmente, l'unica relazione di sottotipizzazione esistente si applica alle operazioni. Intuitivamente, ha senso che uno debba essere autorizzato a sostituire un'operazione che supporta più del set richiesto di funtori. In concreto, per due tipi concreti TIn e TOut, la relazione di sottotipizzazione è

    (TIn => TOut) :>
    (TIn => TOut is Adj), (TIn => TOut is Ctl) :>
    (TIn => TOut is Adj + Ctl)

dove A :> B indica che B è un sottotipo di A. In modo diverso, B è più restrittivo di A in modo che sia possibile usare un valore di tipo B ovunque sia necessario un valore di tipo A. Se un oggetto chiamabile si basa su un argomento (elemento) di tipo A, un argomento di tipo B può essere sostituito in modo sicuro perché se fornisce tutte le funzionalità necessarie.

Questo tipo di polimorfismo si estende alle tuple in quanto una tupla di tipo B è un sottotipo di tipo tupla A se contiene lo stesso numero di elementi e il tipo di ogni elemento è un sottotipo del tipo di elemento corrispondente in A. Questa operazione è nota come sottotipizzazione profondità. Attualmente non è disponibile alcun supporto per la sottotipizzazione larghezza, ovvero non esiste alcuna relazione di sottotipo tra due tipi struct o un tipo struct e qualsiasi tipo predefinito. L'esistenza dell'operatore unwrap, che consente di estrarre una tupla contenente tutti gli elementi denominati, ne impedisce l'esecuzione.

Nota

Per quanto riguarda i chiamabili, se un oggetto chiamabile elabora un argomento di tipo A, è anche in grado di elaborare un argomento di tipo B. Se un oggetto chiamabile viene passato come argomento a un altro chiamabile, deve essere in grado di elaborare qualsiasi elemento necessario per la firma del tipo. Ciò significa che se il chiamabile deve essere in grado di elaborare un argomento di tipo B, qualsiasi chiamabile in grado di elaborare un argomento più generale di tipo A può essere passato in modo sicuro. Al contrario, ci aspettiamo che se è necessario che il callable passato restituisca un valore di tipo A, la promessa di restituire un valore di tipo B è sufficiente, poiché tale valore fornirà tutte le funzionalità necessarie.

Il tipo di operazione o funzione è controvariante nel tipo di argomento e covariante nel tipo restituito. A :> B implica quindi che per qualsiasi tipo concreto T1,

    (B → T1) :> (A → T1), and
    (T1 → A) :> (T1 → B) 

dove qui può significare una funzione o un'operazione e vengono omesse eventuali annotazioni per le caratteristiche. Sostituendo A rispettivamente con (B → T2) e (T2 → A) e sostituendo B con (A → T2) e (T2 → B), porta alla conclusione che, per qualsiasi tipo concreto T2,

    ((A → T2) → T1) :> ((B → T2) → T1), and
    ((T2 → B) → T1) :> ((T2 → A) → T1), and
    (T1 → (B → T2)) :> (T1 → (A → T2)), and
    (T1 → (T2 → A)) :> (T1 → (T2 → B)) 

L'induzione segue che ogni riferimento indiretto aggiuntivo inverte la varianza del tipo di argomento e lascia invariata la varianza del tipo restituito.

Nota

Ciò rende anche chiaro quale comportamento di varianza delle matrici deve essere; il recupero di elementi tramite un operatore di accesso agli elementi corrisponde alla chiamata di una funzione di tipo (Int -> TItem), dove TItem è il tipo degli elementi nella matrice. Poiché questa funzione viene passata in modo implicito durante il passaggio di una matrice, è necessario che le matrici siano covarianti nel tipo di elemento. Le stesse considerazioni riguardano anche le tuple, che non sono modificabili e quindi covarianti rispetto a ogni tipo di elemento. Se le matrici non erano modificabili, l'esistenza di un costrutto che consente di impostare elementi in una matrice e quindi accettare un argomento di tipo TItem, implica che anche le matrici devono essere controvarianti. L'unica opzione per i tipi di dati che supportano il recupero e l'impostazione degli elementi è quindi invariante, ovvero non esiste alcuna relazione di sottotipizzazione; B[] non è un sottotipo di A[] anche se B è un sottotipo di A. Nonostante il fatto che le matrici in Q# siano non modificabili, sono invarianti anziché covarianti. Ciò significa, ad esempio, che un valore di tipo (Qubit => Unit is Adj)[] non può essere passato a un oggetto chiamabile che richiede un argomento di tipo (Qubit => Unit)[]. Mantenere le matrici invarianti consente una maggiore flessibilità correlata alla gestione e all'ottimizzazione delle matrici nel runtime, ma potrebbe essere possibile modificarle in futuro.