我正在学习函数式编程,我正在尝试理解协方差和逆变概念。我现在的问题是:我真的不知道什么时候应该将协方差和逆变应用于泛型类型。在具体的例子中,是的,我可以确定。但一般来说,我不知道哪个一般规则。
例如,这是我研究过的一些规则:
- 如果泛型类型充当参数:使用逆变。 (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])
方法参数作为一个整体处于逆变位置,A
在Iterator
内处于协变位置,但-[+[A]] = -[A]
,所以A
实际上在这个签名内处于逆变位置,类需要说-A
。这是有道理的,外部用户正在传递一堆A
,所以它应该是逆变的。
同样,在
def method(comp: Comparator[A])
整个方法参数处于逆变位置,而A
在Comparator
内处于逆变位置,因此将它们组合-[-[A]] = +[A]
,您会看到A
实际上处于协变位置。这也是有道理的。当您将A
传递到Comparator
时,外部用户可以控制它的功能,因此有点像将该A
返回给他们。