【问题标题】:Isn't the argument type co- not contra-variant?参数类型不是协变的吗?
【发布时间】:2012-10-30 14:10:05
【问题描述】:

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

函数在其参数类型上是逆变的,而在其参数类型上是协变的 他们的返回类型

例如在 Java 中,让 Dog 扩展 Animal。并设一个函数:

void getSomething(Animal a){

我有函数调用

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

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

【问题讨论】:

标签: java scala


【解决方案1】:

这是how functions are defined in Scala

trait Function1 [-T1, +R]  extends AnyRef

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

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

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

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

【讨论】:

  • 另外一件事是,逆变类型只能出现在你写东西的地方(即参数类型),而协变类型只能出现在你读东西的地方(即返回类型)跨度>
【解决方案2】:

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

【讨论】:

  • 添加“额外的东西”不会扩大但会缩小定义,因为符合最具体的子类(狗)的对象比更一般的(动物)的对象少。在接受更多可能实例的意义上,Animal 定义更广泛。
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 那么声明:“函数的参数类型是逆变的,返回类型是协变的”在 Java 环境中无效吗?
  • 目前,是的,但并不是说它声明了 Java 函数的虚假,而是它声明了 Java 中不存在的实体。
  • 实际上,Scala 也没有函数类型。 Scala 中的函数只是具有单一方法的常规对象。事实上,Scala 中的函数在 完全 中的伪造方式与在 Java 中的伪造方式相同。函数参数和返回值的协变和逆变同样适用于 Java interface Function1&lt;T, R&gt; 和 Scala trait Function1[T, R]谈论向 Java 添加函数类型,但在 Java 9 之前没有。
  • 只是想知道,BGGA 提案不是包含完整的函数类型层次结构吗?
【解决方案4】:

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

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

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

val h: Vertebrate =&gt; Primate = f andThen g

但我可以用子类型替换g

val gChild: Animal =&gt; Primate

不破坏可组合性。而gChild 正是g 的子类型,因为我们在其参数中定义了函数逆变。作为结论,您可以看到,如果您想捕捉和保留函数可组合性的思想,就必须以这种方式定义函数。 你可以找到更多的细节和一些图形来帮助你理解这个主题here

【讨论】:

    猜你喜欢
    • 2016-11-01
    • 2017-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多