代码之家  ›  专栏  ›  技术社区  ›  AZ_

scala协方差,逆方差混淆

  •  0
  • AZ_  · 技术社区  · 7 年前

    我是斯卡拉的新人,真的很困惑。请帮帮我。

    /**
    2    * Remember! In Scala, every function that takes one argument 
    3    * is an instance of Function1 with signature:
    4    *
    5    * trait Function1[-T, +S] extends AnyRef
    6    */
    7   
    8   class Vehicle(val owner: String)
    9   class Car(owner: String) extends Vehicle(owner)
    10  
    11  object Printer {
    12
    13    val cars = List(new Car("john"), new Car("paul"))
    14
    15    def printCarInfo(getCarInfo: Car => AnyRef) {
    16      for (car <- cars) println(getCarInfo(car))
    17    }
    18  }
    19  
    20  object Customer extends App {
    21
    22   val getOwnerInfo: (Vehicle => String) = _.owner
    23   
    24   Printer.printCarInfo(getOwnerInfo)
    25  }
    

    此代码取自 https://medium.com/@sinisalouc/variance-in-java-and-scala-63af925d21dc

    规则是:

    函数在其输入类型中是协变的,并且 其返回类型中的反变式来自Liskov替换 原理(LSP)。它说如果t支持 与U相同的操作及其所有操作要求更少(或相同) 并提供比U中相应操作更多(或相同)的 (子类型是反身的,因此S<:S)。

    所以我的问题是,如果我可以传递一个需要超级类型的子类型(代码行编号15和22以上),那么为什么下面的代码不起作用呢?

    class MyClass extends AnyRef
    class MySubClass extends MyClass
    
    abstract class Class {
      val f1: (Any) => Any = ???
      val f2: (Any) => Boolean = ???
      val f3: (MyClass) => Any = ???
      val f4: (MySubClass) => Boolean = ???
      val f5: (Any) => Nothing = ???
      val f6: (MyClass) => Null = ???
    
      val f: (MyClass) => Boolean = f4; //Error
    }
    

    更新 所以实际上,它就像给函数传递一个参数,所以我得到的参数可以是反变的[-t]返回可以是协变的[+s]

        class MyClass extends AnyRef
        class MySubClass extends MyClass
    
        abstract class Class {
          val f1: (Any) => Any = ???
          val f2: (MyClass) => Boolean = ???
          val f3: (MyClass) => Any = ???
          val f4: (MySubClass) => Boolean = ???
          val f5: (Any) => Nothing = ???
          val f6: (MyClass) => Null = ???
    
          val f: (MySubClass) => AnyVal = f2
    }
    

    这是一个有效的代码 MyClass 就像在等级制度中上升 Boolean 就像在等级制度中记录下来。

    2 回复  |  直到 7 年前
        1
  •  1
  •   vvg    7 年前

    让我们看看 printCarInfo 参数。接受的功能很小 Car 作为参数返回 AnyRef 以下内容: getCarInfo: Car => AnyRef .

    在scala中,可以使用 trait Function1[-T, +S] extends AnyRef

    Function1 参数化 -T +S 两种类型。第一个类型表示函数的参数。 小型车 在我们的例子中。它是反变体(减号),也就是说你可以通过 super-type . +S 表示返回类型,它是协变的(加号),这意味着您可以传递任何子类型。

    打印信息 调用和类型的参数 Vehicle => String 通过。 车辆是car的超级类型,字符串是anyref的子类型,因此它满足标准。

    所以为什么变元处于反变位置,而返回类型是协变的。让我们试着假设相反的论点:

    打印信息 接受函数类型: Vehicle=>AnyRef getOwnerInfo Car=>AnyRef

    当你试图实现printcarinfo时,我们可以处理任何争论,不仅是汽车,还有卡车和自行车。 显然调用 Printer.printCarInfo(getOwnerInfo) 当您试图从卡车或自行车中获取OwnerInfo时失败,而您的方法实现只能处理汽车。 希望一切都很清楚。

    关于你的代码。相反:你可以分配 f4: MysSubClass => Boolean = f 它会起作用的。

        2
  •  1
  •   joel DeyaEldeen    7 年前

    您的代码无法编译,因为否则,例如

    class MyOtherSubClass extends MyClass
    

    你的

    val f: (MyClass) => Boolean
    

    可以接受 MyOtherSubClass 作为参数,例如 f(new MyOtherSubClass()) ,但那是电话 f4(new MyOtherSubClass()) .但是 肌热亚类 不是 MySubClass ,所以你会打电话给 f4 用错误的类型