18

我理解协方差和反方差这两个术语。但是有一件小事我无法理解。在 coursera 上的“Scala 中的函数式编程”课程中,Martin Ordersky 提到:

函数的参数类型是逆变的,返回类型是协变的

因此,例如在 Java 中, let Dogextends Animal。让一个函数为:

void getSomething(Animal a){

我有函数调用

Dog d = new Dog();
getSomething(d)

所以基本上发生的事情就是这样Animal a = d。根据wiki协方差是“将更宽转换为窄”。上面我们正在从狗转换为动物。那么参数类型不是协变而不是逆变吗?

4

4 回答 4

32

这是在 Scala 中定义函数的方式

trait Function1 [-T1, +R]  extends AnyRef

在英语中,参数T1是逆变的,结果类型R是协变的。这是什么意思?

当一段代码需要一个Dog => Animal类型的函数时,你可以提供一个Animal => Animal类型的函数,这要归功于参数的逆变(你可以使用更广泛的类型)。

Dog => Dog由于结果类型的协方差,您还可以提供类型的函数(您可以使用更窄的类型)。

这实际上是有道理的:有人想要一个将狗变成任何动物的功能。你可以提供一个函数来改变任何动物(包括狗)。您的函数也只能返回狗,但狗仍然是动物。

于 2012-11-10T12:27:02.027 回答
4

转换DogAnimal将窄转换为宽,因此它不是协方差。

于 2012-11-10T12:15:17.440 回答
1

我记得在 2007 年阅读 Scala Book 时,我被这句话弄糊涂了。Martin 说得好像他在谈论一种语言特性,但在那句话中他只陈述了关于函数的一般事实。具体来说,Scala 仅通过常规trait来模拟该事实。由于 Scala 具有声明站点差异,因此表达这些语义对于该语言来说是很自然的。

另一方面,Java 泛型仅支持使用点变化,因此最接近 Java 中函数类型协/逆变的方法是在每个使用点手动编码:

public int secondOrderFunction(Function<? super Integer, ? extends Number> fn) {
     ....
}

(假设一个适当声明的接口Function<P, R>P代表参数类型和R返回类型)。自然地,由于这段代码在客户端手中,并且根本不特定于函数,所以关于参数类型/返回类型差异的声明不适用于 Java 的任何语言特性。它仅适用于更广泛的意义,与功能的性质有关。

Java 8 将引入闭包,这意味着一流的函数,但是,根据 Jörg 在下面的评论,实现将不包括完整的函数类型。

于 2012-11-10T12:21:27.270 回答
0

我认为关于将Dog转换为Animal的原始问题已经得到澄清,但可能有趣的是,函数在其参数中定义为逆变,在其返回类型中定义为协变是有原因的。假设您有两个功能:

val f: Vertebrate => Mammal = ??? val g: Mammal => Primate = ???

当我们谈论函数时,您会期望函数组合成为您的原始操作之一。实际上,您可以组合 f 和 g ( gof ) 并获得一个函数:

val h: Vertebrate => Primate = f andThen g

但我可以g用一个子类型替换:

val gChild: Animal => Primate

在不破坏可组合性的情况下。AndgChild是 的子类型,g正是因为我们在其参数中定义了 Function 逆变。作为结论,您可以看到,如果您想捕捉和保留函数可组合性的概念,就必须以这种方式定义函数。您可以在此处找到更多详细信息和一些有助于消化该主题的图形

于 2018-07-24T09:58:34.530 回答