我在 Recoil 源代码中偶然发现了 typescript/index.d.ts 的这一部分:
export class AbstractRecoilValue<T> {
__tag: [T];
__cTag: (t: T) => void; // for contravariance
key: NodeKey;
constructor(newKey: NodeKey);
}
在这里如何__cTag
作为逆变的鉴别器?
这是整个上下文:Github
我在 Recoil 源代码中偶然发现了 typescript/index.d.ts 的这一部分:
export class AbstractRecoilValue<T> {
__tag: [T];
__cTag: (t: T) => void; // for contravariance
key: NodeKey;
constructor(newKey: NodeKey);
}
在这里如何__cTag
作为逆变的鉴别器?
这是整个上下文:Github
我认为“用于逆变”可能应该改为“防止协变”,但无论如何我都会解释发生了什么。假设您有一个类型函数type F<T> = ...
。如果你有两种类型A
和B
where B extends A
,比如
interface A { a: string }
interface B extends A { b: number }
declare let a: A;
declare let b: B;
a = b; // okay
b = a; // error
F<A>
如果有的话,你能说一下和之间的关系F<B>
吗?有两个有趣的案例需要考虑:
F<T>
是协变的T
:无论何时B extends A
,那么F<B> extends F<A>
。由于变宽T
会变宽F<T>
,变窄T
会变窄F<T>
,因此可以说与一起F<T>
变化T
。它“协变”,或者是协变的。函数类型的返回类型是协变的。在 TypeScript 中,对象类型在其属性类型中被认为是协变的(尽管这在编写时不合理,但它很有用)。 Array<T>
是协变的T
(即使在编写时这不合理,但它很有用)。[T]
中的协变也是如此T
。
F<T>
是逆变的T
:无论何时B extends A
,那么F<A> extends F<B>
。由于加宽T
会变窄F<T>
和变窄T
会变宽F<T>
,你可以说与F<T>
不同 T
。它“反变化”,或者是逆变的。函数类型(启用编译器选项)的--strictFunctionTypes
参数类型是逆变的。(t: T) => void
中的逆变也是如此T
。在 TypeScript 中,对象类型的属性键类型也被认为是逆变的。
在上面的例子中,当我们说“F<T>
在”中是协变的,T
我们也意味着它不是逆变的。反之亦然;当我们说“F<T>
在”中是逆变的,T
我们也意味着它不是协变的。在 中是[T]
协变的(但不是逆变的),在 中T
是(t: T) => void
逆变的(但不是协变的)T
。但是我们也可以考虑这种情况:
F<T>
在 T中是双变量的:F<T>
在 中既是协变的又是逆变的T
。在完全健全的类型系统中,除非F<T>
完全不依赖,否则不会发生这种情况T
。但在 TypeScript 中,方法类型(或所有--strictFunctionTypes
禁用的函数类型)在其参数类型中被视为双变量。另一种不健全但有用的情况。
最后:
F<T>
在 中是不变的T
:F<T>
在 中既不是协变的也不是逆变的T
。F<A>
和F<B>
when之间没有简单的关系B extends A
。这往往是最常见的情况。从某种意义上说,协变和逆变是“脆弱的”,那么如果你组合协变和逆变类型,你往往会得到不变类型。这就是当你在定义中合并[T]
时会发生的事情。该属性在 中是协变的(但不是逆变的),并且该属性在 中是逆变的(但不是协变的)。通过将它们放在一起,在 中是不变的。(t: T) => void
AbstractRecoilValue<T>
_tag
T
_cTag
T
AbstractRecoilValue<T>
T
所以大概_cTag
是这样添加的,这样AbstractRecoilValue<T>
在 中是不变的T
而不是协变的T
。如果您将其注释掉,您可以看到行为上的差异:
declare class AbstractRecoilValue<T> {
__tag: [T];
// __cTag: (t: T) => void; // for contravariance
key: NodeKey;
constructor(newKey: NodeKey);
}
declare let arvA: AbstractRecoilValue<A>;
declare let arvB: AbstractRecoilValue<B>;
arvA = arvB; // okay
arvB = arvA; // error!
您可以看到它AbstractRecoilValue<B>
可以分配给AbstractRecoilValue<A>
,但反之则不然。AbstractRecoilValue<T>
中的协变也是如此T
。但是如果我们恢复__cTag
:
declare class AbstractRecoilValue<T> {
__tag: [T];
__cTag: (t: T) => void; // for contravariance
key: NodeKey;
constructor(newKey: NodeKey);
}
declare let arvA: AbstractRecoilValue<A>;
declare let arvB: AbstractRecoilValue<B>;
arvA = arvB; // error!
arvB = arvA; // error!
那么这两个赋值都不可接受,因此AbstractRecoilValue<T>
在T
.
如果您想知道为什么对 施加这样的限制AbstractRecoilValue<T>
,我无法回答,因为我不确定它的用途......不过,这似乎超出了所问问题的范围。