首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么是子类型?

为什么是子类型?
EN

Stack Overflow用户
提问于 2018-01-31 14:08:55
回答 2查看 183关注 0票数 2

我正在努力理解contravariance,是如何工作的。考虑以下几句:

令人费解的是,如果A是B的一个亚型,则FB是FA的一个亚型。

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

协方差比较清楚:

协方差表示,如果B是A的子类型,则FB是FA类型的子类型。

第一部分是,FB是的子类型,第二个是B是的子类型。

EN

回答 2

Stack Overflow用户

发布于 2018-01-31 15:14:03

在这个问题的注释中使用Json示例:

代码语言:javascript
复制
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}" }"""

这两种方法都是可以接受的:

代码语言:javascript
复制
writeJson(List(Circle(1), Circle(2)), circleWriter)
writeJson(List(Circle(1), Circle(2)), shapeWriter)

并最终导致

代码语言:javascript
复制
// 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的一个子类型。

票数 3
EN

Stack Overflow用户

发布于 2018-02-01 00:49:19

我有一种直觉,我发现这有助于理解协方差和反方差。但请注意,这并不是一个严格的定义。直觉如下:

  1. 如果某些类只输出A类型的值,这与说类的用户只能从类读取类型A的值相同,那么它在A类型中是协变的。
  2. 如果某些类只接受A类型的值作为输入,这与表示类的用户只能将类型A的值写入类相同,则在A类型中是相反的。

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

代码语言:javascript
复制
trait Producer[A] {
   def produce():A
}

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

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

现在考虑一下方法connect

代码语言:javascript
复制
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。哪个Consumer可以处理这个问题?显然是Consumer[Main]Consumer[Base],因为每个Main都是Base。但是,Consumer[Child]不能安全地处理这个问题,因为并不是每个Main都是Child

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

代码语言:javascript
复制
def connect[A <: B, B](producer:Producer[A], consumer:Consumer[B]): Unit = {
  val value = producer.produce()
  consumer.consume(value)
}

换句话说,我们明确表示有两种不同的泛型类型AB,其中一个是另一个的父类。

另一种解决方案是修改ProducerConsumer类型,使Producer[A]类型的参数接受在此上下文中安全的任何Producer,并且类似地,类型Consumer[A]的参数将接受在此上下文中安全的任何Consumer。正如您可能已经注意到的,Producer的规则是“协变”的,但是“消费者”的规则是“反变体”(请记住,您希望Consumer[Base]Consmer[Main]的一个安全的子类型)。因此,另一种解决方案是编写:

代码语言:javascript
复制
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]也是安全的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48544153

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档