【问题标题】:Contravariance why is the subtype?逆变为什么是子类型?
【发布时间】:2018-01-31 14:08:55
【问题描述】:

我试图了解 逆变,它是如何工作的。考虑以下句子:

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

这句话让我很困惑。在第一部分F[B] is subtype of F[A],突然为什么第二部分是A is a subtype of B?是自相矛盾吗?

协方差更清晰:

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

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

【问题讨论】:

  • 就是这样。协方差是我们理解的“自然”。逆变意味着 F[B] 是 F[A] 的子类型,而 A 是 B 的子类型。 A 和 F[A] 是不同的东西,所以它并不矛盾,只是令人困惑:) 你可以检查这个答案,我认为它是很有帮助stackoverflow.com/questions/27414991/…
  • JsonWriter[Shape] is a subtype of JsonWriter[Circle] because Circle is a subtype of Shape 对我来说根本没有意义。
  • 逆变是协方差的“另一个方向”,另一种表述方式是contravariance means F[B] is a subtype of F[A] if A is a supertype of B
  • 逆变真的那么令人惊讶吗?有很多相反的事情,你不会三思而后行。例如,负数的值和绝对大小是逆变的:值越大,大小越小,反之亦然。

标签: scala contravariance


【解决方案1】:

在本题的 cmets 中使用 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,但由于Function1 的声明:trait Function1[-T1, +R],我们可以将Shape => String 传递给它。第一种类型 (T1) 是逆变的。

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

【讨论】:

  • 你的最后一句话对我来说听起来很矛盾,这听起来对我来说,第一次是shapecircle的子类型,第二次circleshape的子类型,所以让我很困惑。
  • "first is shape a subtype of circle" - 这不是真的,因为您不能将它从复杂类型的上下文中取出。你必须Shape => StringCircle => String 的子类型,你不能只是摆脱=> ... 的东西。 (这令人困惑,但 contraditory 和 contravariance 共享相同的前缀并非巧合!)
【解决方案2】:

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

  1. 如果某个类只输出A类型的值,相当于说该类的用户只能从该类中读取A类型的值,它在@987654323类型中是协变的@
  2. 如果某个类只接受A类型的值作为输入,这相当于说该类的用户只能将A类型的值写入类,它在类型上是逆变的A

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

trait Producer[A] {
   def produce():A
}

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

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

现在考虑方法connect

def connect[A](producer:Producer[A], consumer:Consumer[A]): Unit = {
  val value = producer.produce()
  consumer.consume(value)
}

如果您想一想connect 不是以最通用的方式编写的。考虑类型层次结构Parent <:>Main <:>Child。

  1. 对于固定的Consumer[Main],它可以同时处理MainChild,因为任何Child 实际上都是Main。所以Consumer[Main] 可以安全地连接到Producer[Main]Producer[Child]
  2. 现在考虑固定Producer[Main]。它产生Main。哪个Consumers 可以处理?显然Consumer[Main]Consumer[Base] 因为每个Main 都是Base。然而Consumer[Child] 不能安全地处理它,因为不是每个Main 都是Child

因此,创建最通用的connect 的一种解决方案是这样编写:

def connect[A <: B, B](producer:Producer[A], consumer:Consumer[B]): Unit = {
  val value = producer.produce()
  consumer.consume(value)
}

换句话说,我们明确地说有两种不同的泛型类型AB,一种是另一种的父级。

另一种解决方案是修改ProducerConsumer 类型,使Producer[A] 类型的参数可以接受在此上下文中安全的任何Producer,并且类似于@987654367 类型的参数@ 将接受在此上下文中安全的任何 Consumer。您可能已经注意到Producer 的规则是“协变的”,但“消费者”的规则是“逆变的”(因为请记住,您希望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] 可以安全使用Consumer[Base] 也是安全的。

【讨论】:

    猜你喜欢
    • 2016-10-28
    • 2018-12-24
    • 1970-01-01
    • 2021-09-30
    • 2011-02-02
    • 2015-04-25
    相关资源
    最近更新 更多