Scala中的协方差和方差反转

8
在《Scala快速编程》中,它说道:函数参数是逆变的,返回值类型是协变的。这很直观易懂,然而在同一主题中,它也提到:在函数参数内部,方差会翻转 - 参数是协变的。并且它以IteratorfoldLeft方法为例。
 def foldLeft[B](z : B)(op : (B, A) => B) : B 

我不太清楚它的含义。

我尝试了一些博客,如

  1. http://www.artima.com/pins1ed/type-parameterization.html

  2. http://blog.kamkor.me/Covariance-And-Contravariance-In-Scala/

  3. http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/

但是没有清楚的理解。
3个回答

4

函数在其参数类型上是协变的,在其返回类型上是逆变的,例如:

trait Function1[-T1, +R] extends AnyRef 

trait Function2[-T1, -T2, +R] extends AnyRef 

在这里,T1T2、...、Tn(其中n ≤ 22)是参数,R是返回类型

在高阶函数中(接受函数作为参数的函数),一个参数可以具有传递到函数中的类型参数,例如trait Iterable中的foldLeft函数。

Iterable声明如下:

trait Iterable[+A] extends AnyRef

foldLeft被声明为

def foldLeft[B](z : B)(op : (B, A) => B) : B 

由于A被声明为协变,因此它可以用作返回类型。但是在这里它是一个参数类型,因为

trait Function2[-T1, -T2, +R] extends AnyRef 

因为op: (B, A) => BFunction2的文本类型,所以需要将其作为参数传递。
关键在于特征Function2在其参数类型上是反变的
因此,在方法参数中出现协变类型是由于:

特征Function2在其参数类型上是反变的。

这被称为方差翻转
  1. 协变的翻转是反变。
  2. 反变的翻转是协变的。
  3. 翻转是不变的是不变的。
这就是为什么不变性可能出现在任何位置(协变/反变)的原因。

3
这里关键在于一个函数是另一个函数的子类型意味着什么。如果您认为A->B是C->D的子类型,那么输入类型逆变(C是A的子类型),输出类型协变(B是D的子类型)应该不陌生。
现在考虑接受其他函数作为参数的函数。例如,考虑(A->B)->B。我们只需两次应用相同的推理即可。参数是类型为A->B的函数,返回类型是B。提供类型为C->B的函数作为输入类型需要满足什么条件?由于输入类型中函数是逆变的,因此C->B必须是A->B的子类型。但是,正如我们在第一段中讨论的那样,这意味着A必须是C的子类型。因此,在第一段推理的基础上进行两次应用后,我们发现(A->B)->B在A位置上是协变的。
您可以用更复杂的函数类似地推理。实际上,您应该自己想办法证明:如果某个位置在奇数个箭头的左侧,那么它就是逆变的;在偶数个箭头的左侧则是协变的。

在我看来,这个答案实际上解释了方差翻转背后的“原因”。 - Naitree
在我看来,这个答案像进行严格的数学推导一样解释了原因。 - runzhi xiao

0

首先,将函数视为类或者是一个 typeclass。考虑它的类型 Function1[-A,+B]。 假设我们有以下代码:

class x
class y extends b

现在我有两个如下所示的函数:

val test1:x=>Int = //do something
val test2:y=>int = //do something

现在如果我有另一个像下面这样的方法,

def acceptFunction(f: y => Unit, b: B) = //do something

根据类型签名Function1[-A,+B],由于逆变性,我可以将test2test1传递给acceptFunction。 有点像test1<:test2

这与说函数的参数是协变的是完全不同的事情。

class  Fruit { def name: String="abstract" }
class Orange extends Fruit { override def name = "Orange" }
class Apple extends Fruit { override def name = "Apple" }

你可以写下以下内容:
 testM(new Apple())
 def testM(fruit:Fruit)={}

但你不能写这个,

     testM(new Fruit())
     def testM(fruit:Apple)={}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接