参数类型不是反变体吗?
我理解术语协方差和反方差。 但有一件小事我无法理解。 在课程“Scala中的function编程”课程中,Martin Ordersky提到:
函数在其参数类型中是逆变的,在它们的返回类型中是共变量
因此,例如在Java中,让Dog
扩展Animal
。 让一个函数是:
void getSomething(Animal a){
我有函数调用
Dog d = new Dog(); getSomething(d)
所以基本上发生的事情是Animal a = d
。 根据维基的协方差是“将范围扩大到狭窄”。 而且我们正在从狗转变为动物。 SO不是参数类型协变而不是逆变?
这是在Scala中定义函数的方式 :
trait Function1 [-T1, +R] extends AnyRef
在英语中,参数T1
是逆变的,结果类型R
是协变的。 这是什么意思?
当某段代码需要Dog => Animal
类型的函数时,你可以提供Animal => Animal
类型的函数,这要归功于参数的逆变(你可以使用更宽泛的类型)。
你也可以提供Dog => Dog
类型的function,这要归功于结果类型的协方差(你可以使用更窄的类型)。
这实际上是有道理的:有人想要一个function将狗变成任何动物。 您可以提供转换任何动物(包括狗)的function。 此外,您的function只能返回狗,但狗仍然是动物。
将Dog
转换为Animal
正在将范围转换为更宽,因此它不是协方差。
我记得当我在2007年阅读Scala书时,被这句话弄糊涂了.Martin把它当作一个语言特征来表达,但在那句话中他只说了一般关于function的事实。 具体来说,Scala只是通过常规特征对事实进行建模。 由于Scala具有声明站点方差,因此表达这些语义对于语言来说是很自然的。
另一方面,Java Generics仅支持使用站点方差,因此最接近Java的函数类型的共同/逆转是在每个使用站点手动编码:
public int secondOrderFunction(Function super Integer, ? extends Number> fn) { .... }
(假设适当声明的接口Function
, P
代表参数类型, R
代表返回类型)。 当然,由于此代码掌握在客户端手中,而不是特定于函数,因此有关param类型/返回类型方差的声明不适用于Java的任何语言特性。 它仅适用于更广泛的意义,涉及function的性质 。
Java 8将引入闭包,这意味着一流的function,但是,根据下面的Jörg的评论,实现将不包括完全成熟的函数类型。
我认为关于将Dog转换为Animal的原始问题已经得到了澄清,但可能有意思的是,有一个原因使得函数在其参数中被定义为逆变,并且在其返回类型中具有协变性。 假设您有两个function:
val f: Vertebrate => Mammal = ??? val g: Mammal => Primate = ???
在我们讨论函数时,您会希望函数组合在您的原始操作中。 实际上,你可以编写f和g( gof )并获得一个函数:
val h: Vertebrate => Primate = f andThen g
但我可以用子类型替换g
:
val gChild: Animal => Primate
不破坏可组合性。 gChild
是g
的子类型,因为我们在其参数中定义了函数逆变量。 作为结论,您可以看到,如果要捕获并保留函数可组合性的概念,则必须以这种方式定义函数 。 您可以在此处找到有助于消化此主题的更多详细信息和一些图形