为什么函数[-A1,...,+ B]不允许任何超类型作为参数?

时间:2012-05-15 15:27:23

标签: function scala covariance contravariance

我相信人们可以将协方差(至少是对象)定义为“使用较窄(子)类型的值代替某种较宽(超级)类型的值的能力”,并且这种相反性是与此完全相反。

显然,Scala函数是函数[-A1,...,+ B]的实例,用于逆变参数类型A1等,以及协变返回类型B.虽然这对于函数的子类型很方便,但不应该上面的定义意味着我可以将任何超类型作为参数传递?

请告诉我哪里弄错了。

5 个答案:

答案 0 :(得分:60)

协方差和逆变是的质量而不是参数的质量。 (它们是依赖于参数的品质,但它们会对类进行陈述。)

因此,Function1[-A,+B]意味着一个带有A超类的函数可以被视为原始函数的子类。

让我们在实践中看到这一点:

class A
class B extends A
val printB: B => Unit = { b => println("Blah blah") }
val printA: A => Unit = { a => println("Blah blah blah") }

现在假设您需要一个知道如何打印B的函数:

def needsB(f: B => Unit, b: B) = f(b)

你可以传递printB。但你也可以 传入printA,因为它也知道如何打印B s(以及更多!),就像A => Unit是子类一样B => Unit。这正是逆变意味着什么。这并不意味着您可以将Option[Double]传递给printB并获得除编译时错误之外的任何内容!

(协方差是另一种情况:M[B] <: M[A]如果B <: A。)

答案 1 :(得分:26)

这个问题很古老,但我认为更明确的解释是调用Liskov替换原则:关于超类的一切都应该适用于它的所有子类。你应该可以用SubFoo做一些你可以用Foo做的事情,也许更多。

假设我们有Calico&lt;:Cat&lt;:Animal,和Husky&lt;:Dog&lt ;: Animal。我们来看Function[Cat, Dog]。对此有何正确的陈述?有两个:

(1)您可以传入任何Cat(所以Cat的任何子类)

(2)您可以在返回值

上调用任何Dog方法

Function[Calico, Dog] <: Function[Cat, Dog]有意义吗?不,超类的语句对于子类是不正确的,即语句(1)。您不能将任何Cat传递给只接受印第安猫的猫。

Function[Animal, Dog] <: Function[Cat, Dog]有意义吗?是的,关于超类的所有陈述都适用于子类。我仍然可以传入任何猫 - 实际上我可以做的更多,我可以传递任何动物 - 我可以在返回值上调用所有Dog方法。

所以A <: B暗示Function[B, _] <: Function[A, _]

现在,Function[Cat, Husky] <: Function[Cat, Dog]有意义吗?是的,关于超类的所有陈述都适用于子类;我仍然可以传入一个Cat,我仍然可以在返回值上调用所有Dog方法 - 实际上我可以做更多的事情,我可以在返回值上调用所有Husky方法。

Function[Cat, Animal] <: Function[Cat, Dog]有意义吗?不,超类的语句对于子类是不正确的,即语句(2)。我不能在返回值上调用Dog上的所有可用方法,只能调用Animal上可用的方法。

所以使用Function[Animal, Husky]我可以用Function[Cat, Dog]完成所有操作:我可以传入任何Cat,我可以在返回的值上调用所有Dog方法。我可以做得更多:我可以传递其他动物,我可以调用赫斯基上可用的方法,这些方法在Dog上没有。所以它是有道理的:Function[Animal, Husky] <: Function[Cat, Dog]。第一个类型参数可以用超类替换,第二个用子类替换。

答案 2 :(得分:6)

这里有两个独立的想法。一种是使用子类型来允许将更具体的参数传递给函数(称为 subsumption )。另一个是如何检查函数本身的子类型。

对于函数的参数的类型检查,您只需要检查给定的参数是声明的参数类型的子类型。结果也必须是声明类型的子类型。这是你实际检查子类型的地方。

参数的对立/协方差&amp;结果只是在您想要检查给定的函数类型是否是另一个函数类型的子类型时的因素。因此,如果参数的类型为Function[A1, ... ,B],则参数必须是函数类型Function[C1, ..., D],其中A1 <: C1 ...D <: B

此推理并非特定于Scala,并适用于具有子类型的其他静态类型语言。

答案 3 :(得分:0)

简化说明

class A
class B extends A
val printA: A => Unit = { a => println("Blah blah blah") }
printA(new A())   //"Blah blah blah"
printA(new B())   //"Blah blah blah"

协方差规则:

如果BA的子类型,那么printA[A]printA[B]的子类型

由于printA[B]是超类,因此我们可以使用printA(new B())

答案 4 :(得分:-1)

协变意味着从更宽(超)转变为更窄(子)。例如,我们有两个类:一个是动物(超级),另一个是猫然后使用协变,我们可以将动物转换成猫。

Contra-variant与协变相反,这意味着cat to animal。

不变意味着它无法转换。