在设计具有泛型类型的类时,何时应使用协方差和逆变



我正在学习函数式编程,我正在尝试理解协方差和逆变概念。我现在的问题是:我真的不知道什么时候应该将协方差和逆变应用于泛型类型。在具体的例子中,是的,我可以确定。但一般来说,我不知道哪个一般规则。

例如,这是我研究过的一些规则:

  • 如果泛型类型充当参数:使用逆变。 (1(
  • 如果泛型类型充当 rerun 值:使用协方差。 (二(

在我所知道的一些语言中,这个概念也使用这些约定。例如:in关键字表示协方差(In Scala 是 +(,out关键字表示逆变(在 Scala 中是 -(。第(1(点很容易理解。但是在第 (2( 点,我看到了例外:

  • methodA(Iterator<A>)A 应该是协方差
  • methodA(Comparator<A>)A应该是逆变的。

所以这里的例外是:虽然两种情况都使用泛型类型作为输入,但一种应该是协方差,另一种应该是逆变。我的问题是:在设计类时,我们是否有任何一般规则来决定协方差/逆变。

谢谢

协方差和逆变就像算术中的数字符号,当你将一个位置与方差嵌套在另一个位置时,组合是不同的。

比较:

1] +(+a) = +a
2] -(+a) = -a
3] +(-a) = -a
4] -(-a) = +a

trait +[+A] { def make(): A } // Produces an A
trait -[-A] { def break(a: A) } // Consumes an A
1]
// Produces an A after one indirection: x.makeMake().make()
trait ++[+A] { def makeMake(): +[A] }
+[+[A]] = +[A]
2]
// Consumes an A through one indirection: x.breakMake(new +[A] { override def make() = a })
trait -+[-A] { def breakMake(m: +[A]) }
-[+[A]] = -[A]
3]
// Consumes an A after one indirection: x.makeBreak().break(a)
trait +-[-A] { def makeBreak(): -[A] }
+[-[A]] = -[A]
4]
// Produces an A through one indirection
// Slightly harder to see than the others
// x.breakBreak(new -[A] { override def break(a: A) = {
//   you have access to an A here, so it's like it produced an A for you
// }})
trait --[+A] { def breakBreak(b: -[A]) }
-[-[A]] = +[A]

所以当你有

def method(iter: Iterator[A])

方法参数作为一个整体处于逆变位置,AIterator内处于协变位置,但-[+[A]] = -[A],所以A实际上在这个签名内处于逆变位置,类需要说-A。这是有道理的,外部用户正在传递一堆A,所以它应该是逆变的。

同样,在

def method(comp: Comparator[A])

整个方法参数处于逆变位置,而AComparator内处于逆变位置,因此将它们组合-[-[A]] = +[A],您会看到A实际上处于协变位置。这也是有道理的。当您将A传递到Comparator时,外部用户可以控制它的功能,因此有点像将该A返回给他们。

相关内容

  • 没有找到相关文章

最新更新