Q#類型模型
在討論如何定義Q#操作和函數時,我們已經看到來自可調參數的輸入和輸出每個都與它們的類型一起表示。 在這一點上,退一步討論這些類型會更有幫助。 特別是,我們注意到Q#是一種強類型語言,因此仔細使用這些類型可以幫助編譯器在編譯時提供有關Q#程序的強大保證。
警告
爲了提供最強有力的保證,Q#中的類型之間的轉換必須通過調用表示該轉換的函數來顯式進行。 作爲Microsoft.Quantum.Extensions.Convert命名空間的一部分提供了各種這樣的函數。 另一方面向兼容類型的向上轉換隱式發生。
Q#提供了兩種可以直接使用的基本類型,以及多種從其他類型生成新類型的方法。 我們在本節的其餘部分描述每一個。
原始類型
Q#語言提供了一組可用於整個操作和功能的原始類型 。
-
Int
:表示64位有符號整數,例如:107
,-5
。 -
Double
:表示雙精度浮點數,例如:0.0
,-1.3
和4e-7
。 -
Bool
:表示一個條件,可以是true
或false
。 -
Pauli
:代表保利矩陣之一,泡利,保利PauliI
,保利或PauliZ
。 -
Result
:表示計算基礎上的度量結果,對於 ket0
Range
:表示連續的整數序列,用start..step..stop
表示。 例如: 1..2..7
代表序列1,3,5,7- 。
-
String
:表示在發生錯誤或診斷事件時要報告的消息。
另外,Q#定義了一個基本類型Qubit
來模擬目標機器中的一個量子位的不透明引用。 Qubit
類型的值不能直接在Q#中使用,但可以傳遞給目標機器定義的操作(例如門和測量),以執行有趣的事情。 我們將在關於使用量子的章節中更詳細地考慮Qubit
類型。
元組類型
給定零個或多個不同類型的T0
, T1
,..., Tn
,我們可以將一個新的元組類型表示爲(T0, T1, ..., Tn)
。 新元組類型的值是由元組中每種類型的值序列形成的元組。 例如, (3, false)
是一個元組,其類型是元組類型(Int, Bool)
。 用於構造(Int, (Qubit, Qubit))
組類型的類型本身可以是元組,如(Int, (Qubit, Qubit))
。 但是,這樣的嵌套總是有限的,因爲元組類型在任何情況下都不能包含它們自己。
元組是Q#中使用的一個強大的概念,它將值集中到一個值中,使其更易於傳遞。 特別是,使用元組符號,我們可以表示每個操作和可調用只需要一個輸入並返回一個輸出。
在Q#中,只有一個元素的元組類型被認爲與該元素的類型是等價的,這個屬性被稱爲單例元組等價 。 例如,類型Qubit
, (Qubit)
和((((Qubit))))
之間沒有區別。 特別是,這意味着輸入元組或輸出元組類型有一個字段的操作或函數可以被認爲是採用單個參數或返回單個值。
數組類型
給定任何其他類型T
,類型T[]
表示該類型值的數組。 例如,整數集合表示爲Int[]
,而(Bool, Pauli)
值的數組數組表示爲(Bool, Pauli)[][]
。
通過在數組元素周圍使用方括號,可以在Q#源代碼中寫入數組值,如[PauliI; PauliX; PauliY; PauliZ]
[PauliI; PauliX; PauliY; PauliZ]
[PauliI; PauliX; PauliY; PauliZ]
。 每個元素的類型必須完全匹配,因爲Q#中沒有“基本”類型。
警告
數組創建後通常不能更改數組的元素。 爲了更改數組的元素,它必須綁定到一個可變變量 。
或者,可以使用new
關鍵字從其大小創建一個數組:
let zeros = new Int[13];
// new also allows for creating empty arrays:
let emptyRegister = new Qubit[0];
正如我們上面討論的那樣 ,這對於可變數組通常更有用,因爲使用new
關鍵字創建的數組的各個元素本身並不常用。
在任何一種情況下,一旦數組構造完畢,就可以使用內置的Length
函數來獲取Int
的元素數目。 數組可以使用方括號進行下標,下標具有Int
類型或Range
類型,以獲取包含數組元素子集的單個元素或新數組。 數組的下標是從零開始的:
let arr = [10; 11; 36; 49];
let ten = arr[0]; // 10
let odds = arr[1..2..4]; // [11; 49]
操作和功能類型
如上所述,操作和函數是Q#中的值。 這些值的類型是根據每個操作和函數獲取和返回的輸入和輸出元組的類型構造的。 爲了在實踐中看到這一點,我們來考慮一下上面的ApplyTwice
示例:
operation ApplyTwice(op : ((Qubit) => ()), target : Qubit) : () {
...
在這裏,我們看到操作聲明指定op
具有類型((Qubit) => ())
,這意味着op
的類型是一個操作類型,並且具有作爲其有效值的操作,該操作接受(Qubit)
併產生()
的輸出。 我們可以使用->
而不是=>
以相同的方式指示函數。 每個箭頭之前和之後的類型可以是我們希望的任何類型,包括其他操作或函數類型。 例如,我們可以將上面定義的函數SquareOperation
傳遞給任何類型的輸入((Qubit) => ())) -> ((Qubit) => ())
。 非正式地,我們可以將該類型讀作“一個經典函數,它對單個量子位執行操作,並在單個量子位上返回操作”。
爲了使用操作類型的Controlled
變體和Adjoint
變體,我們需要指出該類型的值支持我們希望調用的變體。 這是通過在操作類型中添加約束來完成的,如(Qubit => () : Adjoint)
Adjoint (Qubit => () : Adjoint)
,它表示一個可操作的一個量子位的可操作操作,以產生一個空元組作爲其輸出。
用戶定義的類型
在Q#中構造新類型的最後一種方法是使用用戶定義的類型 (UDT)。 對於任何元組類型T
,我們可以用newtype
語句聲明一個新的用戶定義類型,它是T
的子類型。 例如,在Microsoft.Quantum.Canon命名空間中,複數被定義爲用戶定義的類型:
newtype Complex = (Double, Double);
這個語句創建一個新類型,它實際上是特定元組類型的標籤。 新類型的值是通過使用類型的名稱作爲函數創建的:
let realUnit = Complex(1.0, 0.0);
let imaginaryUnit = Complex(0.0, 1.0);
與普通的元組類型一樣,用戶定義類型的構成元素可以使用解構來訪問。 這讓我們可以將訪問函數寫入用戶定義類型的結構中,例如:
function Re(z : Complex) : Double {
let (re, im) = z;
return re;
}
function Im(z : Complex) : Double {
let (re, im) = z;
return im;
}
除了爲可能複雜的元組類型提供簡短別名之外,使用UDT的一個顯着優點是它們可以記錄特定值的意圖。 回到Complex
的例子,人們也可以將2D極座標定義爲用戶定義的類型:
newtype Polar = (Double, Double);
即使Complex
和Polar
都來自(Double, Double)
,這兩種類型在Q#中完全不兼容,從而最大限度地減少了用極座標意外調用複雜數學函數的風險,反之亦然。 通過這種方式,用戶定義的類型可以在C和其他這樣的語言中起到類似的struct
類型的作用。