Subtyping 和變異數
Q# 只支援幾個轉換機制。 只有在套用二元運算符、評估條件表達式或建構數位列常值時,才會發生隱含轉換。 在這些情況下,會決定通用的超類型,並自動執行必要的轉換。 除了這類隱含轉換之外,可以透過函數調用進行明確轉換,而且通常是必要的。
目前,唯一存在的子樣子樣式關聯性會套用至作業。 直覺上,應該允許一個替代支持超過所需函式集合的作業。 具體而言,針對任何兩個具象類型 TIn
和 TOut
,subtyping 關聯為
(TIn => TOut) :>
(TIn => TOut is Adj), (TIn => TOut is Ctl) :>
(TIn => TOut is Adj + Ctl)
其中 A :> B
表示 B
是 A
的子類型。 以不同的字組表達,B
會比 A
限制更多,在需要 B
類型值的任何位置都可以使用類型 A
的值。 如果可呼叫檔依賴類型 A
的引數 (項目),則可以安全地取代 B
類型的引數,因為如果有提供所有必要的功能。
如果 Tuple 包含相同數目的專案,且每個專案的型別類型是對應專案類型的A
子類型,則這種多型會延伸至 Tuple。B
A
這就是所謂的深度 subtyping。 目前不支援 寬度子類型,也就是說,任何兩個使用者定義型別或使用者定義型別與任何內建類型之間沒有子類型關聯性。 運算子的存在 unwrap
,可讓您擷取包含所有具名和匿名專案的 Tuple,可防止此情況。
注意
對於可呼叫者而言,如果可呼叫者處理類型的 A
自變數,則它也可以處理 類型的 B
自變數。 如果將可呼叫者當做自變數傳遞至另一個可呼叫者,則必須能夠處理類型簽章可能需要的任何專案。 這表示,如果可呼叫端必須能夠處理 類型的 B
自變數,任何能夠安全地傳遞型別較一般自變數的 A
可呼叫者。 相反地,如果我們要求傳遞的可呼叫端傳回 類型的值,則傳回型A
B
別值的承諾就已足夠,因為該值會提供所有必要的功能。
作業或函式類型在其引數類型中為逆變,而在其傳回類型中為共變。
A :> B
因此,表示對於任何具象類型 T1
,
(B → T1) :> (A → T1), and
(T1 → A) :> (T1 → B)
其中此處的 →
可以表示函式或作業,並省略特性的任何註釋。
A
分別使用 (B → T2)
和 (T2 → A)
取代 ,並以 B
(A → T2)
和 (T2 → B)
分別取代,會導致任何具體類型 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))
藉由進行感應,每個額外的間接取值都會反轉引數類型的變異數,並讓傳回類型的變異數保持不變。
注意
這也可讓您清楚地了解陣列的變異數行為必須為何;透過項目存取運算子來取得項目,會對應至叫用類型 (Int -> TItem)
的函式,其中 TItem
是陣列中的元素類型。 因為此函式會在傳遞陣列時以隱含方式傳遞,所以會遵循陣列在其項目類型中必須為共變。 相同的考量也適用於元組,這些元組是不可變的,因此對於每個項目類型都是共變。
如果陣列不是固定的,則建構的存在可讓您設定陣列中的專案,因此採用類型的 TItem
自變數,表示陣列也需要反變數。 因此,支援取得和設定項目的資料類型,唯一的選項就是不變,這表示沒有任何 subtyping 關聯;B[]
不是A[]
的子類型,即使 B
是 A
的子類型。 儘管中的 Q# 陣列 是不可變的,但它們是不可變的,而不是共變數。 例如,這表示型 (Qubit => Unit is Adj)[]
別的值無法傳遞至需要 型 (Qubit => Unit)[]
別自變數的可呼叫者。
讓陣列保持不變,可提供更多的彈性,與在執行階段中處理和最佳化陣列的方式有關,但未來可能會修改該陣列。