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

子类型化和方差

Q# 仅支持一些转换机制。 只有在应用二元运算符、评估条件表达式以及构造数组字面量时,才会发生隐式转换。 在这些情况下,需确定一个共同的超类型并自动执行必要的转换。 除了此类隐式转换此外,还可以通过函数调用进行显式对话,而且通常是必要的。

目前,唯一存在的子类型关系适用于操作。 直观地说,应该允许替换支持超过所需函子集的操作是有道理的。 具体来说,对于任何两个具体类型 TInTOut,子类型关系为

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

其中 A :> B 表示 BA 的子类型。 换句话说,BA 更严格,因此不管需要哪种 A 类型的值,都可以使用 B 类型的值。 如果可调用对象依赖于 A 类型的参数(项),则可以安全地替换 B 类型的参数,前提是提供了所有必要的功能。

这种多态性扩展到了元组,由于元组类型 B 包含相同的项数并且每个项的类型都是 A 中对应项类型的子类型,因此它是元组类型 A 的子类型。 这称为“深度子类型化”。 目前不支持“宽度子类型”,也就是说,任何两个用户定义类型或某个用户定义类型与任何内置类型之间没有子类型关系。 unwrap 运算符的存在防止了这种情况(该运算符支持提取包含所有命名和匿名项的元组)。

注意

对于可调用对象,如果可调用对象处理 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 类型参数的构造,这将意味着数组也必须是逆变的。 因此,针对支持获取和设置项的数据类型的唯一选项是不变的,这意味着没有任何子类型关系;即使 BA 的子类型,B[] 也不是 A[] 的子类型。 尽管 Q# 中的数组是不可变的,但它们是不变的,而不是协变的。 例如,这意味着不能将 (Qubit => Unit is Adj)[] 类型的值传递给需要 (Qubit => Unit)[] 类型参数的可调用对象。 通过使数组保持不变,这可在运行时实现灵活地处理和优化数组,但将来可能会对其进行修改。