bjt*_*t38 30 scala function covariance contravariance
我相信人们可以将协方差(至少对象来说)定义为"使用较窄(子)类型的值代替某种较宽(超级)类型的值的能力",并且这种相反性恰好相反.这个.
显然,Scala函数是函数[-A1,...,+ B]的实例,用于逆变参数类型A1等,以及协变返回类型B.虽然这对于函数的子类型很方便,但上述定义不应该意味着我可以传递任何超类型作为参数?
请告诉我哪里弄错了.
Rex*_*err 62
协方差和逆变是该类的质量而不是参数的质量.(它们是依赖于参数的品质,但它们会对类进行陈述.)
因此,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") }
Run Code Online (Sandbox Code Playgroud)
现在假设您需要一个知道如何打印的函数B:
def needsB(f: B => Unit, b: B) = f(b)
Run Code Online (Sandbox Code Playgroud)
你可以传递printB.但是你也可以传入printA,因为它也知道如何打印Bs(以及更多!),就像A => Unit是它的子类一样B => Unit.这正是逆变意味着什么.这并不意味着你可以通过Option[Double]进入printB,并得到什么,但一个编译时错误!
(协方差是另一种情况:M[B] <: M[A]如果B <: A.)
csj*_*s24 27
这个问题是陈旧的,但我认为更明确的解释是调用Liskov替换原则:关于超类的所有事情都应该适用于它的所有子类.你可以用SubFoo做一些你可以用Foo做的事情,甚至更多.
假设我们有Calico <:Cat <:Animal,和Husky <:Dog <:Animal.我们来看看Function[Cat, Dog].对此有何正确的陈述?那里有两个:
(1)你可以传入任何猫(所以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].第一个类型参数可以用超类替换,第二个用子类替换.
这里有两个独立的想法.一种是使用子类型来允许将更具体的参数传递给函数(称为包含).另一个是如何检查函数本身的子类型.
对于函数的参数的类型检查,您只需检查给定的参数是声明的参数类型的子类型.结果也必须是声明类型的子类型.这是您实际检查子类型的地方.
当您想要检查给定函数类型是否是另一个函数类型的子类型时,参数和结果的对立/协方差仅考虑因素.因此,如果参数具有类型Function[A1, ... ,B],则参数必须是函数类型Function[C1, ..., D]where A1 <: C1 ...和D <: B.
此推理并非特定于Scala,并适用于具有子类型的其他静态类型语言.