如何检查函数中元素的协变和逆变位置?

Cha*_*kar 6 types functional-programming scala covariance contravariance

这是我阅读的有关scala中的逆变和协方差的文章之一的代码片段.但是,我无法理解scala编译器抛出的错误消息"错误:协变类型A出现在值pet2的类型A中的逆变位置

class Pets[+A](val pet:A) {
  def add(pet2: A): String = "done"
}
Run Code Online (Sandbox Code Playgroud)

我对这段代码片段的理解是,Pets是协变的并且接受A的子类型的对象.但是,函数add仅接受A类型的参数.Being covariant意味着Pets可以获取A类及其子类型的参数.那怎么会抛出错误呢.从哪里出现逆变问题.

对上述错误消息的任何解释都将非常有用.谢谢

And*_*kin 18

TL; DR:

  • 你的Pets类可以通过返回成员变量来生成类型A的值pet,因此a Pet[VeryGeneral]不能是子类型Pet[VerySpecial],因为当它生成某些东西时VeryGeneral,它不能保证它也是一个实例VerySpecial.因此,它不能逆变.

  • 您的Pets类可以通过将它们作为参数传递来使用类型A的值add.因此,a Pet[VerySpecial]不能成为宠物的子类型Pet[VeryGeneral],因为它会阻塞任何不是的输入VerySpecial.因此,你的班级不能协变.

唯一剩下的可能性是:Pets必须是不变的A.


举例说明:协方差与逆差:

我将利用这个机会展示这部漫画的改进版和更加严谨的版本.它的一个例证协方差逆变 与分型和报关现场方差注释编程语言的概念(显然,即使Java的人发现了它足够的启发,尽管这个问题是关于使用现场方差).

首先,插图:

协方差逆变可笑的

现在用可编译的Scala代码进行更详细的描述.

对比差异的解释(图1的左侧部分)

考虑以下能源层次结构,从非常一般到非常具体:

class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables
Run Code Online (Sandbox Code Playgroud)

现在考虑一个Consumer[-A]具有单一consume(a: A)方法的特征:

trait Consumer[-A] {
  def consume(a: A): Unit
}
Run Code Online (Sandbox Code Playgroud)

让我们实现一些这个特性的例子:

object Fire extends Consumer[EnergySource] {
  def consume(a: EnergySource): Unit = a match {
    case b: Bamboo => println("That's bamboo! Burn, bamboo!")
    case v: Vegetables => println("Water evaporates, vegetable burns.")
    case c: EnergySource => println("A generic energy source. It burns.")
  }
}

object GeneralistHerbivore extends Consumer[Vegetables] {
  def consume(a: Vegetables): Unit = a match {
    case b: Bamboo => println("Fresh bamboo shoots, delicious!")
    case v: Vegetables => println("Some vegetables, nice.")
  }
}

object Panda extends Consumer[Bamboo] {
  def consume(b: Bamboo): Unit = println("Bamboo! I eat nothing else!")
}
Run Code Online (Sandbox Code Playgroud)

现在,为什么Consumer必须逆变A?让我们尝试实例化一些不同的能源,然后将它们提供给各种消费者:

val oilBarrel = new EnergySource
val mixedVegetables = new Vegetables
val bamboo = new Bamboo

Fire.consume(bamboo)                // ok
Fire.consume(mixedVegetables)       // ok
Fire.consume(oilBarrel)             // ok

GeneralistHerbivore.consume(bamboo)           // ok
GeneralistHerbivore.consume(mixedVegetables)  // ok
// GeneralistHerbivore.consume(oilBarrel)     // No! Won't compile

Panda.consume(bamboo)               // ok
// Panda.consume(mixedVegetables)   // No! Might contain sth Panda is allergic to
// Panda.consume(oilBarrel)         // No! Pandas obviously cannot eat crude oil
Run Code Online (Sandbox Code Playgroud)

结果是:Fire可以消耗可以消耗的所有东西GeneralistHerbivore,反过来GeneralistHerbivore可以消耗Panda可以吃的所有东西.因此,只要我们只关心消耗能源的能力, Consumer[EnergySource]可以在Consumer[Vegetables]需要的地方替代,并且 Consumer[Vegetables]可以在Consumer[Bamboo]需要的地方替代.因此,它是有道理的Consumer[EnergySource] <: Consumer[Vegetables]Consumer[Vegetables] <: Consumer[Bamboo],即使型参数之间的关系是完全相反的:

type >:>[B, A] = A <:< B

implicitly:          EnergySource  >:>          Vegetables
implicitly:          EnergySource                           >:>          Bamboo
implicitly:                                     Vegetables  >:>          Bamboo

implicitly: Consumer[EnergySource] <:< Consumer[Vegetables]
implicitly: Consumer[EnergySource]                          <:< Consumer[Bamboo]
implicitly:                            Consumer[Vegetables] <:< Consumer[Bamboo]
Run Code Online (Sandbox Code Playgroud)

协方差解释(图1的右侧部分)

定义产品层次结构:

class Entertainment
class Music extends Entertainment
class Metal extends Music // yes, it does, seriously^^
Run Code Online (Sandbox Code Playgroud)

定义可以生成类型值的特征A:

trait Producer[+A] {
  def get: A
}
Run Code Online (Sandbox Code Playgroud)

定义不同专业水平的各种"来源"/"生产者":

object BrowseYoutube extends Producer[Entertainment] {
  def get: Entertainment = List(
    new Entertainment { override def toString = "Lolcats" },
    new Entertainment { override def toString = "Juggling Clowns" },
    new Music { override def toString = "Rick Astley" }
  )((System.currentTimeMillis % 3).toInt)
}

object RandomMusician extends Producer[Music] {
  def get: Music = List(
    new Music { override def toString = "...plays Mozart's Piano Sonata no. 11" },
    new Music { override def toString = "...plays BBF3 piano cover" }
  )((System.currentTimeMillis % 2).toInt)
}

object MetalBandMember extends Producer[Metal] {
  def get = new Metal { override def toString = "I" }
}
Run Code Online (Sandbox Code Playgroud)

BrowseYoutube是最通用的来源Entertainment:它可以给你基本上任何类型的娱乐:猫视频,杂耍小丑,或(意外)一些音乐.这个通用的来源Entertainment由图1中的原型小丑代表.

RandomMusician已经有点更加专业化,至少我们知道,这个对象产生音乐(即使是没有限制任何特定类型).

最后,MetalBandMember非常专业:该get方法保证只返回非常特定的Metal音乐类型.

让我们尝试Entertainment从这三个对象中获取各种各样的东西:

val entertainment1: Entertainment = BrowseYoutube.get   // ok
val entertainment2: Entertainment = RandomMusician.get  // ok
val entertainment3: Entertainment = MetalBandMember.get // ok

// val music1: Music = BrowseYoutube.get // No: could be cat videos!
val music2: Music = RandomMusician.get   // ok
val music3: Music = MetalBandMember.get  // ok

// val metal1: Entertainment = BrowseYoutube.get   // No, probably not even music
// val metal2: Entertainment = RandomMusician.get  // No, could be Mozart, could be Rick Astley
val metal3: Entertainment = MetalBandMember.get    // ok, because we get it from the specialist
Run Code Online (Sandbox Code Playgroud)

我们看到这三个Producer[Entertainment],Producer[Music]并且Producer[Metal]可以产生某种形式Entertainment.我们只看到Producer[Music]Producer[Metal]保证生产Music.最后,我们看到只有非常专业的才能Producer[Metal]保证生产Metal而不是其他.因此,Producer[Music]Producer[Metal]可以取代一个Producer[Entertainment].A Producer[Metal]可以代替a Producer[Music].一般而言,可以为不太专业的生产者提供更具体产品的生产者:

implicitly:          Metal  <:<          Music
implicitly:          Metal                      <:<          Entertainment
implicitly:                              Music  <:<          Entertainment

implicitly: Producer[Metal] <:< Producer[Music]
implicitly: Producer[Metal]                     <:< Producer[Entertainment]
implicitly:                     Producer[Music] <:< Producer[Entertainment]
Run Code Online (Sandbox Code Playgroud)

产品之间的子类型关系与产品生产者之间的子类型关系相同.这就是协方差的意思.


相关链接

  1. 关于类似的讨论? extends A? super B比较:在Java 8 的Java 8 Comparator comparing()静态函数

  2. 经典的" flatMap在我自己的Either实现中什么是正确的类型参数"问题:类型L出现在逆变位置Either[L, R]