编译器如何自动计算协变和逆变?
How can compiler compute automatically co- and contravariance?
请注意这是一个关于编译器内部结构的问题。
我刚刚读到 [1],当为泛型类型引入变体时,C# 团队正在考虑他们是否应该自动计算类型是协变还是逆变。当然,这已经成为历史了,但我仍然想知道这是怎么做到的?
采用所有方法(不包括构造函数)并检查类型是否在 in
或 out
位置是否足够?
[1] Jeffrey Richter,CLR via C#,第 4 版,第 281 页。
示例中:
interface I<T, S, R>
{
S M(T t, R r);
T N();
}
你可以使用当前的C#编译器看看是否允许在T
、S
和R
前面放置out
(协方差标记) ,分别和 in
(逆变标记)相同。由于 T
既用作参数类型(M
方法的第一个参数)又用作 return 类型(N
方法),它既不能有 out
或 in
(当前的 C# 编译器可以判断,如果您尝试其中任何一个,它都会抱怨)。对于 S
,它被用作 return 类型,因此它不能有 in
(当前的 C# 编译器知道)。而对于R
,它被用作参数类型,所以它不能有out
(当前的C#编译器知道)。
C# 的设计者决定让程序员选择是否需要泛型变体。所以对于这个例子,有四种合法的方法来编写带有差异标记的 I<,,>
接口:
// 1
interface I<T, S, R>
{
S M(T t, R r);
T N();
}
// 2
interface I<T, out S, R>
{
S M(T t, R r);
T N();
}
// 3
interface I<T, S, in R>
{
S M(T t, R r);
T N();
}
// 4
interface I<T, out S, in R>
{
S M(T t, R r);
T N();
}
设计者的另一个选择是总是使这个接口类型在S
中协变而在R
中协变,给程序员任何机会到"disable"这个。在那种情况下,每个类型参数都会自动获得 "best" 可能的通用变体。在此上下文中不需要关键字 out
和 in
。
泛型委托类型也是如此。
现已删除的答案中的 link 是针对我的文章的,该文章解释了确定方差有效性的确切规则,但没有回答您的问题。 link 你真正要找的是我关于为什么 C# 编译器团队拒绝尝试在没有任何语法的情况下计算方差的文章,它在这里:
简而言之,拒绝此功能的原因是:
- 该功能需要对整个程序进行分析。这不仅代价高昂,还意味着一种类型的微小变化可能会导致许多遥远类型的方差选择发生意外变化。
- 方差是您要设计到类型中的东西;它是关于您期望用户如何使用该类型的声明,不仅是今天,而且是永远。这种期望应该编码到程序文本中。
- 很多情况下很难计算出用户的意图,那怎么办呢?您必须通过要求语法来解决它,那么为什么不一直要求它呢?例如:
interface I<V, W>
{
I<V, W> M(I<W, V> x);
}
作为练习,计算 V 和 W 上所有可能的有效方差注释。现在,编译器应该如何进行相同的计算?你用的是什么算法?其次,鉴于这是歧义,你会选择如何解决歧义?
现在,我注意到到目前为止这个答案也没有回答您的问题。你问如何做到这一点,而我给你的只是我们不应该尝试这样做的理由。有很多方法可以做到。
例如,取程序中的每一个泛型,以及这些泛型的每一个类型参数。假设有一百个。那么每个不变的 in 和 out 只有三到百种可能的组合;尝试所有这些,看看哪些有效,然后有一个从获胜者中选择的排名功能。那里的问题当然是 运行.
需要比宇宙年龄更长的时间
现在,我们可以应用智能 p运行ing 算法来表示 "any choice where T is in and is also known to be used in an output position is invalid",所以不要检查任何这些情况。现在我们有数百个这样的谓词必须全部应用以确定适用的方差有效性集的情况。正如我在上面的示例中所指出的,要弄清楚某物何时实际处于输入或输出位置可能非常棘手。所以这也可能是一个非首发。
啊,但是这个想法意味着对代数系统的谓词进行分析是一种潜在的好技术。我们可以构建一个生成谓词的引擎,然后对其应用复杂的 SMT solver。那会有需要大量计算的糟糕情况,但现代 SMT 求解器在典型情况下非常好。
但是对于一个对用户几乎没有价值的功能来说,所有这些工作量太大了。
请注意这是一个关于编译器内部结构的问题。
我刚刚读到 [1],当为泛型类型引入变体时,C# 团队正在考虑他们是否应该自动计算类型是协变还是逆变。当然,这已经成为历史了,但我仍然想知道这是怎么做到的?
采用所有方法(不包括构造函数)并检查类型是否在 in
或 out
位置是否足够?
[1] Jeffrey Richter,CLR via C#,第 4 版,第 281 页。
示例中:
interface I<T, S, R>
{
S M(T t, R r);
T N();
}
你可以使用当前的C#编译器看看是否允许在T
、S
和R
前面放置out
(协方差标记) ,分别和 in
(逆变标记)相同。由于 T
既用作参数类型(M
方法的第一个参数)又用作 return 类型(N
方法),它既不能有 out
或 in
(当前的 C# 编译器可以判断,如果您尝试其中任何一个,它都会抱怨)。对于 S
,它被用作 return 类型,因此它不能有 in
(当前的 C# 编译器知道)。而对于R
,它被用作参数类型,所以它不能有out
(当前的C#编译器知道)。
C# 的设计者决定让程序员选择是否需要泛型变体。所以对于这个例子,有四种合法的方法来编写带有差异标记的 I<,,>
接口:
// 1
interface I<T, S, R>
{
S M(T t, R r);
T N();
}
// 2
interface I<T, out S, R>
{
S M(T t, R r);
T N();
}
// 3
interface I<T, S, in R>
{
S M(T t, R r);
T N();
}
// 4
interface I<T, out S, in R>
{
S M(T t, R r);
T N();
}
设计者的另一个选择是总是使这个接口类型在S
中协变而在R
中协变,给程序员任何机会到"disable"这个。在那种情况下,每个类型参数都会自动获得 "best" 可能的通用变体。在此上下文中不需要关键字 out
和 in
。
泛型委托类型也是如此。
现已删除的答案中的 link 是针对我的文章的,该文章解释了确定方差有效性的确切规则,但没有回答您的问题。 link 你真正要找的是我关于为什么 C# 编译器团队拒绝尝试在没有任何语法的情况下计算方差的文章,它在这里:
简而言之,拒绝此功能的原因是:
- 该功能需要对整个程序进行分析。这不仅代价高昂,还意味着一种类型的微小变化可能会导致许多遥远类型的方差选择发生意外变化。
- 方差是您要设计到类型中的东西;它是关于您期望用户如何使用该类型的声明,不仅是今天,而且是永远。这种期望应该编码到程序文本中。
- 很多情况下很难计算出用户的意图,那怎么办呢?您必须通过要求语法来解决它,那么为什么不一直要求它呢?例如:
interface I<V, W>
{
I<V, W> M(I<W, V> x);
}
作为练习,计算 V 和 W 上所有可能的有效方差注释。现在,编译器应该如何进行相同的计算?你用的是什么算法?其次,鉴于这是歧义,你会选择如何解决歧义?
现在,我注意到到目前为止这个答案也没有回答您的问题。你问如何做到这一点,而我给你的只是我们不应该尝试这样做的理由。有很多方法可以做到。
例如,取程序中的每一个泛型,以及这些泛型的每一个类型参数。假设有一百个。那么每个不变的 in 和 out 只有三到百种可能的组合;尝试所有这些,看看哪些有效,然后有一个从获胜者中选择的排名功能。那里的问题当然是 运行.
需要比宇宙年龄更长的时间现在,我们可以应用智能 p运行ing 算法来表示 "any choice where T is in and is also known to be used in an output position is invalid",所以不要检查任何这些情况。现在我们有数百个这样的谓词必须全部应用以确定适用的方差有效性集的情况。正如我在上面的示例中所指出的,要弄清楚某物何时实际处于输入或输出位置可能非常棘手。所以这也可能是一个非首发。
啊,但是这个想法意味着对代数系统的谓词进行分析是一种潜在的好技术。我们可以构建一个生成谓词的引擎,然后对其应用复杂的 SMT solver。那会有需要大量计算的糟糕情况,但现代 SMT 求解器在典型情况下非常好。
但是对于一个对用户几乎没有价值的功能来说,所有这些工作量太大了。