逆变为什么是子类型?

时间:2018-01-31 14:08:55

标签: scala contravariance

我想了解逆转,它是如何运作的。请考虑以下句子:

  

令人困惑的是,逆变意味着F [B]类型是一个子类型   如果A是B

的子类型,则为[A]
这句话让我感到困惑。在第一部分F[B] is subtype of F[A]突然出现第二部分A is a subtype of B的原因?它自相矛盾?

协方差更清晰:

  

协方差意味着类型F [B]是类型F [A]的子类型,如果B   是A的子类型。

第一部分是 F [B]是子类型,第二部分 B是子类型

2 个答案:

答案 0 :(得分:3)

在此问题的评论中使用Json示例:

trait Shape {
  val area: Double
}

case class Circle(radius: Double) extends Shape {
  override val area = math.Pi * radius * radius
}

def writeJson(circles: List[Circle], jsonWritingFunction: Circle => String): String =
  circles.map(jsonWritingFunction).mkString("\n")

def circleWriter(circle: Circle): String =
  s"""{ "type" : "circle writer", radius : "${circle.radius}", "area" : "${circle.area}" }"""

def shapeWriter(shape: Shape): String =
  s"""{ "type" : "shape writer", "area" : "${shape.area}" }"""

然后这两个都是可以接受的:

writeJson(List(Circle(1), Circle(2)), circleWriter)
writeJson(List(Circle(1), Circle(2)), shapeWriter)

结果

// first writeJson
{ "type" : "circle writer", "radius" : "1.0", "area" : "3.141592653589793" }
{ "type" : "circle writer", "radius" : "2.0", "area" : "12.566370614359172" }
// first writeJson
{ "type" : "shape writer", "area" : "3.141592653589793" }
{ "type" : "shape writer", "area" : "12.566370614359172" }

即使jsonWritingFunction期望Circle => String,我们也可以通过Shape => String声明传递Function1trait Function1[-T1, +R]。第一种类型(T1)是逆变的。

因此Shape => StringCircle => String的子类型,因为CircleShape的子类型。

答案 1 :(得分:1)

我有一种直觉,我觉得有助于理解协方差和逆变。请注意,这不是一个严格的定义。直觉如下:

  1. 如果某个类仅输出类型A的值,这与该类的用户只能从类中读取类型A的值相同,则它是协变的类型A
  2. 如果某个类只接受类型A的值作为输入,这与说该类的用户只能将类型A的值写入类的相同,它是类型A
  3. 中的逆变量

    举一个简单的例子,考虑两个接口Producer[A]Consumer[A]

    trait Producer[A] {
       def produce():A
    }
    
    trait Consumer[A] {
       def consume(value:A):Unit
    }
    

    一个只输出A类型的值(因此您从A“读取”Producer[A])而另一个接受它们作为参数(因此您“写”{{1}到A)。

    现在考虑方法Producer[A]

    connect

    如果您想一下这个def connect[A](producer:Producer[A], consumer:Consumer[A]): Unit = { val value = producer.produce() consumer.consume(value) } 不是以最通用的方式编写的。考虑类型层次结构connect&lt ;: Parent<:Main

    1. 对于固定的Child,它可以处理Consumer[Main]Main,因为任何Child实际上都是Child。因此,Main可以安全地连接到Consumer[Main]Producer[Main]
    2. 现在考虑修复Producer[Child]。它产生Producer[Main]。哪个Main可以处理?显然ConsumerConsumer[Main]因为每个Consumer[Base]都是Main。但是,Base无法安全处理,因为并非每个Consumer[Child]都是Main
    3. 因此,创建最通用的Child的一个解决方案就是这样写:

      connect

      换句话说,我们明确说明有两种不同的泛型def connect[A <: B, B](producer:Producer[A], consumer:Consumer[B]): Unit = { val value = producer.produce() consumer.consume(value) } A,另一种是另一种的父类。

      另一种解决方案是修改BProducer类型,使Consumer类型的参数接受在此上下文中安全的任何Producer[A]类似地,类型Producer的参数将接受在此上下文中安全的任何Consumer[A]。正如您可能已经注意到Consumer的规则是“协变”但“消费者”的规则是“逆变”(因为记住您希望Producer成为{{1}的安全子类型})。所以替代解决方案是写:

      Consumer[Base]

      此解决方案更好,因为它通过一次更改涵盖了所有情况。显然,在Consmer[Main]使用trait Producer[+A] { def produce():A } trait Consumer[-A] { def consume(value:A):Unit } def connect[A](producer:Producer[A], consumer:Consumer[A]): Unit = { val value = producer.produce() consumer.consume(value) } 安全的任何情况下,Consumer[Main]也是安全的。