函数在其参数类型中是逆变的,并且在它们的返回类型中是共变量的

时间:2016-12-29 09:03:30

标签: scala covariance contravariance

在发布此问题之前,我已阅读thisthis个答案,但我对此主题的理解仍然有点不清楚,如下所述:

我理解协变和逆变独立意味着什么。

如果我有以下课程:

class Car {}
class SportsCar extends Car {}
class Ferrari extends SportsCar {}

object covar extends App {

    // Test 1: Works as expected

    def test1( arg: SportsCar => SportsCar ) = {
        new SportsCar
    }

    def foo1(arg: Car): Ferrari = { new Ferrari }
    def foo2(arg: SportsCar): Car = { new Ferrari }
    def foo3(arg: Ferrari): Ferrari = { new Ferrari }

    test1(foo1) // compiles
    test1(foo2) // Fails due to wrong return type - violates return type is covariant
    test1(foo3) // Fails due to wrong parameter type - violates param type is contravariant

    // Test 2: Confused - why can I call test2 succesfully with ferrari 
    // as the parameter, and unsuccesfully with a car as the parameter?

    def test2(arg: SportsCar): SportsCar = {
        new Ferrari
    }

    val car = new Car()
    val sportsCar = new SportsCar()
    val ferrari = new Ferrari()

    val f1 = test2(ferrari)  // compiles - why?
    val f2 = test2(car)      // fails - why?

}

如上所述,在测试2中,为什么我可以用法拉利作为参数成功调用test2,并且以汽车作为参数不成功?

语句函数在其参数类型中是否是逆变的,并且返回类型中的共变量仅适用于作为参数传递的函数?我想我没有在声明和我写的2个测试之间做出适当的区分。

1 个答案:

答案 0 :(得分:7)

那是因为你将子类型 co / contravariance混合。

在您的第一个示例中,您需要Function1[-T, +R]。在这种情况下,co / contravariance规则适用于函数类型。在第二个示例中,您遵循简单的子类型规则。

test1中,您传递的foo1 Car => Ferrari一切正常,因为foo1需要Car,但获得SportsCar ,这是一个Car。由于子类型的性质,任何需要Car的方法都可以处理子类型。但是当我们单独讨论子类型时,这些规则不起作用。

如果我们使用test1扩展foo1实际类型,也许它会更清晰:

foo1:

  • 参数类型:
    • 期望:Car
    • 实际:SportsCar
  • 返回类型:
    • 期望:SportsCar
    • 实际:Ferrari

一切顺利。

test2中,规则会发生变化。如果我希望SportsCar你传递任何一辆车,那么我就不能再依赖输入作为跑车而且一切都会中断,但如果你传入一辆实际上是法拉利的法拉利跑车,一切都很好。

再次,让我们排列类型:

TEST2:

  • 第一个案例:
    • 参数类型:
      • 期望:SportsCar
      • 实际:Ferrari(子类型)
    • 结果:大家都很开心
  • 第二个案例:
    • 参数类型:
      • 期望:SportsCar
      • 实际:Car
    • 结果:错误。我们不确定汽车是一辆真正的跑车。