你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

类型参数化

Q# 支持类型参数化运算和函数。 Q# 标准库大量使用参数化可调用对象类型来提供许多有用的抽象,包括函数语言中熟悉的 MappedFold 等函数。

为了激发类型参数化的概念,请考虑函数 Mapped 的示例,该函数将给定函数应用于数组中的每个值,并返回一个包含计算值的新数组。 无需指定输入和输出数组的项类型即可完美地描述此函数。 由于确切的类型不会更改函数 Mapped 的实现,因此应该可以为任意项类型定义此实现;我们想要定义一个“工厂”或“模板”,给定输入和输出数组中项的具体类型,它将返回相应的函数实现。 此概念以类型参数的形式进行形式化。

具体化

任何运算或函数声明都可以指定一个或多个类型参数,这些参数可用作可调用对象的输入和/或输出的类型或部分类型。 例外情况是入口点,它必须是具体的,不能进行类型参数化。 类型参数名称以勾号 (') 开头,可能会在输入和输出类型中多次出现。 与可调用签名中的相同类型参数对应的所有参数必须属于同一类型。

类型参数化的可调用对象需要具体化(即,它必需具有必要的类型参数),然后才能将其作为参数分配或传递,以便所有类型参数都可以替换为具体类型。 如果某个类型是内置类型之一、用户定义类型,或者如果它是当前范围内的具体类型,则该类型被视为具体类型。 下面的示例说明了一个类型在当前范围内是具体的含义,下面将更详细地解释:

    function Mapped<'T1, 'T2> (
        mapper : 'T1 -> 'T2,
        array : 'T1[]
    ) : 'T2[] {

        mutable mapped = new 'T2[Length(array)];
        for (i in IndexRange(array)) {
            set mapped w/= i <- mapper(array[i]);
        }
        return mapped;
    }

    function AllCControlled<'T3> (
        ops : ('T3 => Unit)[]
    ) : ((Bool,'T3) => Unit)[] {

        return Mapped(CControlled<'T3>, ops); 
    }

CControlled 函数在 Microsoft.Quantum.Canon 命名空间中定义。 它将 'TIn => Unit 类型的运算 op 作为参数,并返回 (Bool, 'TIn) => Unit 类型的新运算,该运算应用原始运算,前提是经典位(类型为 Bool)设置为 true;这通常被称为 op 的经典控制版本。

该函数 Mapped 将任意项类型 'T1 的数组作为参数,将给定的 mapper 函数应用于每个项,并返回包含映射项的类型 'T2[] 的新数组。 它在 Microsoft.Quantum.Array 命名空间中定义。 对于此示例,对类型参数进行了编号,以避免通过为两个函数中的类型参数指定相同的名称来使讨论更加混乱。 这不是必需的;不同可调用对象的类型参数可能具有相同的名称,并且所选名称仅在该可调用对象的定义中可见且相关。

函数 AllCControlled 采用一个运算数组,并返回一个新数组,其中包含这些运算的经典控制版本。 调用 Mapped 将其类型参数 'T1 解析为 'T3 => Unit,并将其类型参数 'T2 解析为 (Bool,'T3) => Unit。 解析类型参数由编译器根据给定参数的类型推断。 假设它们由调用表达式的参数隐式定义。 也可以显式指定类型参数,就像对同一行中的 CControlled 所做的那样。 无法推断类型参数时,显式具体化 CControlled<'T3> 是必要的。

类型 'T3AllCControlled 上下文中是具体的,因为它对于 AllCControlled 的每个调用都是已知的。 这意味着,一旦知道程序的入口点(不能进行类型参数化),就知道了每次调用 AllCControlled 的具体类型 'T3,就可以生成该特定类型解析的合适实现。 一旦知道程序的入口点,就可以在编译时消除类型参数的所有用法。 我们将此过程称为“单态化”。

需要一些限制来确保这确实可以在编译时完成,而不是仅在运行时完成。

限制

请考虑以下示例:

    operation Foo<'TArg> (
        op : 'TArg => Unit,
        arg : 'TArg
    ) : Unit {

        let cbit = RandomInt(2) == 0;
        Foo(CControlled(op), (cbit, arg));        
    } 

忽略调用 Foo 将导致无限循环,它用于说明目的。 Foo 使用已传入的原始运算 op 的经典控制版本以及除原始参数之外还包含随机经典位的元组来调用自身。

对于递归中的每次迭代,下一次调用的类型参数 'TArg 解析为 (Bool, 'TArg),其中 'TArg 是当前调用的类型参数。 具体来说,假设使用运算 H 调用 Foo,并且参数 arg 类型为 Qubit。 然后,Foo 将使用类型参数 (Bool, Qubit) 调用自身,接着该参数将使用类型参数 (Bool, (Bool, Qubit)) 调用 Foo,依此类推。 显然,在这种情况下,Foo 不能在编译时单态化。

其他限制适用于调用关系图中仅涉及类型参数化可调用对象的循环。 遍历循环后,需要使用同一组类型参数调用每个可调用对象。

注意

可以减少限制,并对于循环中的每个可调用对象都有此要求,存在有限数量的循环,之后使用原始的类型参数集调用它,例如以下函数的情况:

   function Bar<'T1,'T2,'T3>(a1:'T1, a2:'T2, a3:'T3) : Unit{
       Bar<'T2,'T3,'T1>(a2, a3, a1);
   }

为简单起见,强制执行了限制性更强的要求。 请注意,对于至少涉及一个没有任何类型参数的具体可调用对象的循环,此类可调用对象将确保该循环中的类型参数化可调用对象始终使用一组固定的类型参数进行调用。