如何检查函数中元素的协变和逆变位置? [英] How to check covariant and contravariant position of an element in the function?

查看:133
本文介绍了如何检查函数中元素的协变和逆变位置?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我读过的有关scala中的逆变和协变的文章中的一段代码。但是,我无法理解scala编译器抛出的错误消息错误:协变类型A发生在值pet2的类型A的逆变位置中

  class Pets [+ A](val pet:A){
def add(pet2:A):String =done
}

我对此代码片段的理解是,Pets是协变的并接受作为A的子类型的对象。但是,函数add需要A类型的参数只有协变才意味着宠物可以接受A型及其亚型的参数,那么这应该如何抛出错误呢?从哪里出发,甚至会出现逆转问题。

对上述错误信息的任何解释都非常有帮助。谢谢

TL >


$ b

  • 您的 Pets 类可以生成 >类型的值 A ,通过返回成员变量 pet ,所以一个 Pet [V eryGeneral] 不能是 Pet [VerySpecial] 的子类型,因为它产生的东西 VeryGeneral ,它不能保证它也是 VerySpecial 的一个实例。因此,

  • 您的 Pets 通过将它们作为参数传递给 add 来消费类型 A 值。因此, Pet [VerySpecial] 不能是pet Pet [VeryGeneral] 的子类型,因为它会阻塞任何输入那不是 VerySpecial 。因此,您的课堂不能协同




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






一个例子:协方差与反变换:



我将借此机会介绍一个改进的和明显多于
的严格版本

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

b
$ b

逆变的解释(图1的左半部分)



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

  class EnergySource $ b $ class蔬菜延伸EnergySource 
类Bamboo延伸蔬菜

现在考虑一个特征 Consumer [-A] 有一个消耗(a:A) - 方法:

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

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

  object Fire扩展Consumer [EnergySource ] {
def consume(a:EnergySource):Unit =一个匹配{
case b:Bamboo => println(That's bamboo!Burn,bamboo!)
case v:Vegetables => println(水蒸发,蔬菜灼伤)
案例c:EnergySource => println(通用能源,它燃烧。)
}
}

对象通用食草动物延伸消费者[蔬菜] {
def消费(a:蔬菜) :Unit = a match {
case b:Bamboo => println(新鲜笋,好吃!)
案例v:蔬菜=> println(一些蔬菜,很好。)
}
}

object Panda扩展消费者[竹子] {
def消费(b:竹子):单位= println(Bamboo!我什么都不吃!)
}

现在,为什么消费者必须在 A 中不可逆转?让我们尝试实例化$ b $几个不同的能源,然后将它们提供给各种消费者:

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

Fire.consume(竹子)// ok
Fire.consume(mixedVegetables)// ok
Fire.consume(oilBarrel)// ok

GeneralistHerbivore.consume(竹子)// ok
GeneralistHerbivore.consume(mixedVegetables)// ok
// GeneralistHerbivore.consume (oilBarrel)//不!不会编译

Panda.consume(竹子)// ok
// Panda.consume(mixedVegetables)//否!可能含有......熊猫过敏
// Panda.consume(oilBarrel)//不!熊猫显然不能吃原油

结果是: Fire 可以消耗一切 GeneralistHerbivore 可以消耗的所有东西,
又可以消耗 GeneralistHerbivore code> Panda 可以吃。
因此,只要我们只关心消耗能源的能力,可以代替
Consumer [EnergySource] ,其中消费者[蔬菜] 是必需的,

消费者[蔬菜] 可以代替,其中消费者[Bamboo] 是必需的。
因此,有意义的是消费者[EnergySource]<:消费者[蔬菜]
消费者[蔬菜]< ;:Consumer [Bamboo] ,即使
类型参数之间的关系完全相反:

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

含蓄:EnergySource>:>蔬菜
隐含:EnergySource>:>竹
隐含:蔬菜>:> Bamboo

含蓄:Consumer [EnergySource]<:<消费者[蔬菜]
含蓄:消费者[EnergySource]<:<消费者[竹子]
暗含:消费者[蔬菜]<:<消费者[竹]



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



定义产品层次:

  class娱乐
类音乐扩展娱乐
class Metal延伸音乐//是的,它的确很重要^^

定义一个可以 生成类型 A 的值:

  trait Producer [+ A] {
def get:A
}

定义不同级别专业化的各种来源/生产者:

 对象BrowseYoutube延伸制作人[娱乐] {
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)
}

对象RandomMusician扩展nds Producer [Music] {
def get:Music = List(
新音乐{override def toString =...演奏莫扎特的钢琴奏鸣曲no。 11},
新音乐{覆盖def toString =...播放BBF3钢琴封面}
)((System.currentTimeMillis%2).toInt)
}

object MetalBandMember extends Producer [Metal] {
def get = new Metal {override def toString =I}
}
$ b

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


$ b

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

最后, MetalBandMember 是非常特殊的: get 方法保证只返回
Metal 音乐



让我们尝试获取各种娱乐

  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 //不,可能不是音乐
// val metal2:Entertainment = RandomMusician.get //不,可能是Mozart,可能是Rick Astley
val metal3:Entertainment = MetalBandMember.get //好吧,因为我们从专家处获得

我们看到所有三个 Producer [Entertainment] 制作人[Music] 制作人[Metal] 可以产生某种娱乐
我们发现只有 Producer [Music] Producer [Metal] 保证产生音乐
最后,我们看到只有极其专业的 Producer [Metal] 才能保证
产生 Metal 制作人[Music] 制作人[Metal] 可以替换
作为生产者[娱乐] 。一个制作人[金属] 可以代替制作人[Music]
一般来说,生产者可以为一个较不专业的生产者提供
a更具体类型的产品:

 <$ c $隐含地:金属<:<音乐
隐含:Metal<:<娱乐
隐含:音乐<:<娱乐

含蓄:制作人[金属]<:<制作人[音乐]
含蓄:制作人[金属]<:<制作人[娱乐]
含蓄:制作人[音乐]<:< Producer [Entertainment]

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


This is a code snippet from one of the articles that I read regarding contravariance and covariance in scala. However, I fail to understand the error message thrown by the scala compiler "error: covariant type A occurs in contravariant position in type A of value pet2

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

My understanding of this code snippet is that Pets is covariant and accepts objects that are subtypes of A. However, the function add takes in a parameter of type A only.Being covariant means that the Pets can take parameters of Type A and its subtypes. Then how is this supposed to throw error. From where is the question of contravariance even arising.

Any explanation to the above error message will be highly helpful. Thanks

解决方案

TL;DR:

  • Your Pets class can produce values of type A by returning the member variable pet, so a Pet[VeryGeneral] cannot be a subtype of Pet[VerySpecial], because when it produces something VeryGeneral, it cannot guarantee that it is also an instance of VerySpecial. Therefore, it cannot be contravariant.

  • Your Pets class can consume values of type A by passing them as arguments to add. Therefore a Pet[VerySpecial] cannot be a subtype of pet Pet[VeryGeneral], because it will choke on any input that is not VerySpecial. Therefore, your class cannot be covariant.

The only remaining possibility is: Pets must be invariant in A.


An illustration: Covariance vs. Contravariance:

I'll use this opportunity to present an improved and significantly more rigorous version of this comic. It is an illustration of the covariance and contravariance concepts for programming languages with subtyping and declaration-site variance annotations (apparently, even Java people found it sufficiently enlightening, despite the fact that the question was about use-site variance).

First, the illustration:

Now a more detailed description with compilable Scala code.

Explanation for Contravariance (left part of Figure 1)

Consider the following hierarchy of energy sources, from very general, to very specific:

class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables

Now consider a trait Consumer[-A] that has a single consume(a: A)-method:

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

Let's implement a few examples of this trait:

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!")
}

Now, why does Consumer have to be contravariant in A? Let's try to instantiate a few different energy sources, and then feed them to various consumers:

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

The outcome is: Fire can consume everything a GeneralistHerbivore can consume, and in turn GeneralistHerbivore can consume everything a Panda can eat. Therefore, as long as we care only about the ability to consume energy sources, Consumer[EnergySource] can be substituted where a Consumer[Vegetables] is required, and Consumer[Vegetables] can be substituted where a Consumer[Bamboo] is required. Therefore, it makes sense that Consumer[EnergySource] <: Consumer[Vegetables] and Consumer[Vegetables] <: Consumer[Bamboo], even though the relationship between the type parameters is exactly the opposite:

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]

Explanation for Covariance (right part of Figure 1)

Define a hierarchy of products:

class Entertainment
class Music extends Entertainment
class Metal extends Music // yes, it does, seriously^^

Define a trait that can produce values of type A:

trait Producer[+A] {
  def get: A
}

Define various "sources"/"producers" of varying levels of specialization:

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" }
}

The BrowseYoutube is the most generic source of Entertainment: it could give you basically any kind of entertainment: cat videos, juggling clowns, or (accidentally) some music. This generic source of Entertainment is represented by the archetypical jester in the Figure 1.

The RandomMusician is already somewhat more specialized, at least we know that this object produces music (even though there is no restriction to any particular genre).

Finally, MetalBandMember is extremely specialized: the get method is guaranteed to return only the very specific kind of Metal music.

Let's try to obtain various kinds of Entertainment from those three objects:

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

We see that all three Producer[Entertainment], Producer[Music] and Producer[Metal] can produce some kind of Entertainment. We see that only Producer[Music] and Producer[Metal] are guaranteed to produce Music. Finally, we see that only the extremely specialized Producer[Metal] is guaranteed to produce Metal and nothing else. Therefore, Producer[Music] and Producer[Metal] can be substituted for a Producer[Entertainment]. A Producer[Metal] can be substituted for a Producer[Music]. In general, a producer of a more specific kind of product can be subsituted for a less specialized producer:

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

implicitly: Producer[Metal] <:< Producer[Music]
implicitly: Producer[Metal]                     <:< Producer[Entertainment]
implicitly:                     Producer[Music] <:< Producer[Entertainment]

The subtyping relationship between the products is the same as the subtyping relationship between the producers of the products. This is what covariance means.

这篇关于如何检查函数中元素的协变和逆变位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆