现在您已经看到了如何在Q#中编写有趣的量子程序,本节将进一步介绍几个更高级的主题,这些主题将在未来发挥作用。
通用操作和功能
我们可能希望定义的许多函数和操作实际上并不严重依赖于它们的输入类型,而只是通过其他函数或操作隐式地使用它们的类型。 例如,考虑许多功能语言通用的地图概念; 给定函数f(x)
和值集合x1,x2, dots,xn ,map将返回一个新的集合f(x1),f(x2), ...,f(xn)。 为了在Q#中实现这一点,我们可以利用这个功能是一流的。 我们来写一个Map
的快速示例,使用★作为占位符,并找出我们需要的类型。
function Map(fn : ★ -> ★, values : ★[]) : ★[] {
mutable mappedValues = new ★[Length(values)];
for (idx in 0..Length(values) - 1) {
set mappedValues[idx] = fn(values[idx]);
}
return mappedValues;
}
注意,无论我们替换的实际类型如何,该函数看起来都非常相似。例如,从整数到Paulis的映射看起来与从浮点数到字符串的映射非常相似:
function MapIntsToPaulis(fn : Int -> Pauli, values : Int[]) : Pauli[] {
mutable mappedValues = new Pauli[Length(values)];
for (idx in 0..Length(values) - 1) {
set mappedValues[idx] = fn(values[idx]);
}
return mappedValues;
}
function MapDoublesToStrings(fn : Double -> String, values : Double[]) : String[] {
mutable mappedValues = new String[Length(values)];
for (idx in 0..Length(values) - 1) {
set mappedValues[idx] = fn(values[idx]);
}
return mappedValues;
}
原则上,我们可以为遇到的每一对类型编写一个Map
版本,但这会带来一些困难。 例如,如果我们在Map
发现一个错误,那么我们必须确保修补程序在所有版本的Map
统一应用。 而且,如果我们构造一个新的元组或UDT,那么现在我们还必须构建一个新的Map
来与新类型一起使用。 虽然这对于少数这样的功能来说是容易处理的,但是由于我们收集了与Map
相同形式的越来越多的功能,所以引入新类型的成本在相当短的时间内变得不合理地大。
但是,这种困难的结果很多,因为我们没有给出编译器所需的信息来识别不同版本的Map
是如何相关的。 实际上,我们希望编译器将Map
视为某种从Q# 类型到Q#函数的数学函数。 这个概念通过允许函数和操作具有类型参数以及它们的普通元组参数来形式化。 在上面的例子中,我们希望将Map
视为具有类型参数Int, Pauli
在第一种情况下为Int, Pauli
Double, String
在第二种情况下为Double, String
。 大多数情况下,这些类型参数可以像使用普通类型一样使用:我们使用类型参数的值来创建数组和元组,调用函数和操作,并将其分配给普通变量或可变变量。
Tip
间接依赖的最极端情况是量子比特,其中Q#程序不能直接依赖Qubit
类型的结构,但必须将这些类型传递给其他操作和函数。
回到上面的例子,我们可以看到我们需要Map
有类型参数,一个表示fn
的输入,一个表示fn
的输出。 在Q#中,这是通过在声明中的函数或操作的名称后添加尖括号(即<>
,不包括 braket
!),并列出每个类型参数来编写的。 每个类型参数的名称必须以“tick '
开头,表示它是一个类型参数,而不是普通类型(也称为具体类型)。 因此,对于Map
,我们写道:
function Map<'Input, 'Output>(fn : 'Input -> 'Output, values : 'Input[]) : 'Output {
mutable mappedValues = new 'Output[Length(values)];
for (idx in 0..Length(values) - 1) {
set mappedValues[idx] = fn(values[idx]);
}
return mappedValues;
}
请注意, Map<'Input, 'Output>
的定义与我们之前写出的版本非常相似。 唯一的区别是我们已经明确告知编译器, Map
并不直接依赖于'Input
'Output
和'Output
,而是通过fn
间接使用它们来适用于任何两种类型。 一旦我们以这种方式定义了Map<'Input, 'Output>
,我们就可以把它称为一个普通的函数:
// Represent Z₀ Z₁ X₂ Y₃ as a list of ints.
let ints = [3; 3; 1; 2];
// Here, we assume IntToPauli : Int -> Pauli
// looks up PauliI by 0, PauliX by 1, so forth.
let paulis = Map(IntToPauli, ints);
Tip
编写泛型函数和操作是一个地方,“元组元组”是考虑Q#函数和操作的非常有用的方法。 由于每个函数只需要一个输入并返回一个输出,所以类型'T -> 'U
的输入与任何 Q#函数都匹配。 同样,任何操作都可以传递给'T => 'U
类型的输入。
作为第二个例子,考虑编写一个返回另外两个函数组合的函数的挑战:
function ComposeImpl(outerFn : (B -> C), innerFn : (A -> B), input : A) : C {
return outerFn(innerFn(input));
}
function Compose(outerFn : (B -> C), innerFn : (A -> B)) : (A -> C) {
return ComposeImpl(outerFn, innerFn, _);
}
在这里,我们必须明确指出A
, B
和C
是什么,因此严重限制了我们新的Compose
函数的效用。 毕竟, Compose
只依赖于A
, B
和C
通过 innerFn
和outerFn
。 然后,作为替代,我们可以将类型参数添加到Compose
,以表明它适用于任何 A
, B
和C
,只要这些参数与innerFn
和outerFn
所期望的匹配:
function ComposeImpl<'A, 'B, 'C>(outerFn : ('B -> 'C), innerFn : ('A -> 'B), input : 'A) : 'C {
return outerFn(innerFn(input));
}
function Compose<'A, 'B, 'C>(outerFn : ('B -> 'C), innerFn : ('A -> 'B)) : ('A -> 'C) {
return ComposeImpl(outerFn, innerFn, _);
}
提供Q#标准库的canon提供了一系列这种类型参数化的操作和功能,以便更容易地表达高阶控制流。 这些将在Q#标准库指南中进一步讨论。
借用Qubits
借用机制允许分配在计算过程中可用作临时空间的量子位。 这些量子位通常不处于干净状态,即它们不一定以已知状态(例如 ket0
)初始化。 人们也会说“脏”的量子位,因为他们的状态是未知的,甚至可能与量子计算机存储器的其他部分纠缠在一起。 众所周知的脏量子比特的使用情况是多重控制的CNOT门的实现,其只需要很少的量子位和实现增量。
在canon中有一些使用borrowing
关键字的例子,例如下面定义的MultiControlledXBorrow
函数。 如果controls
表示应该添加到X
操作的控制量子位,那么通过该实现将添加总体的Length(controls)-2
许多脏的添加。
operation MultiControlledXBorrow ( controls : Qubit[] , target : Qubit ) : () {
body {
... // skipping some case handling here
let numberOfDirtyQubits = numberOfControls - 2;
borrowing( dirtyQubits = Qubit[ numberOfDirtyQubits ] ) {
let allQubits = [ target ] + dirtyQubits + controls;
let lastDirtyQubit = numberOfDirtyQubits;
let totalNumberOfQubits = Length(allQubits);
let outerOperation1 =
CCNOTByIndexLadder(
numberOfDirtyQubits + 1, 1, 0, numberOfDirtyQubits , _ );
let innerOperation =
CCNOTByIndex(
totalNumberOfQubits - 1, totalNumberOfQubits - 2, lastDirtyQubit, _ );
WithA(outerOperation1, innerOperation, allQubits);
let outerOperation2 =
CCNOTByIndexLadder(
numberOfDirtyQubits + 2, 2, 1, numberOfDirtyQubits - 1 , _ );
WithA(outerOperation2, innerOperation, allQubits);
}
}
adjoint auto
controlled( extraControls ) {
MultiControlledXBorrow( extraControls + controls, target );
}
controlled adjoint auto
}
请注意,在此示例中广泛使用了With
combinator ---以适用于支持伴随操作的形式(即WithA
---),这是一种良好的编程风格,因为将控制添加到涉及仅将传播控件内部操作。 还要注意的是,除了操作的主体之外,明确提供了操作的controlled
机构的实现,而不是诉诸于controlled auto
语句。 原因在于我们从电路结构中知道如何轻松添加进一步的控制,这与将控制添加到body
中的每个单独门控相比是有益的。
将此代码与另一个canon函数MultiControlledXClean
进行比较是MultiControlledXClean
,它实现了实现乘法控制的X
操作的相同目标,然而,它使用using
机制的几个干净的量子位。
应该注意的是,只有这样的量子位才能在操作范围内借用到,而操作还不在操作范围内,否则编译器会引发异常。