加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

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

发布时间:2020-12-16 18:41:21 所属栏目:安全 来源:网络整理
导读:这是我阅读的有关 scala中的逆变和协方差的文章之一的代码片段.但是,我无法理解scala编译器抛出的错误消息“错误:协变类型A出现在值pet2的类型A中的逆变位置 class Pets[+A](val pet:A) { def add(pet2: A): String = "done"} 我对这段代码片段的理解是,Pet
这是我阅读的有关 scala中的逆变和协方差的文章之一的代码片段.但是,我无法理解scala编译器抛出的错误消息“错误:协变类型A出现在值pet2的类型A中的逆变位置

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

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

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

解决方法

TL; DR:

>您的Pets类可以通过返回成员变量pet来生成类型A的值,因此Pet [VeryGeneral]不能是Pet [VerySpecial]的子类型,因为当它生成VeryGeneral时,它不能保证它也是一个实例非常特别.因此,它不能逆变.
>您的Pets类可以通过将它们作为要添加的参数传递来使用类型A的值.因此宠物[VerySpecial]不能是宠物宠物[VeryGeneral]的子类型,因为它会阻塞任何非特殊的输入.因此,你的班级不能协变.

唯一剩下的可能性是:宠物必须在A中保持不变.

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

我将利用这个机会提出一个改进的,显着的更多
严格的this comic版本.它是协方差和逆变的例证
具有子类型和声明 – 站点方差注释的编程语言的概念
(显然,即便是Java人员也发现它具有足够的启发性,
?尽管问题是关于使用场地差异的事实.

首先,插图:

covariance-contravariance-comic

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

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

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

class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables

现在考虑具有单一消费(a:A)方法的特征消费者[-A]:

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

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

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

现在,为什么消费者必须在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

结果是:火可以消耗GeneralistHerbivore可以消耗的所有东西,
反过来,GeneralistHerbivore可以消耗熊猫可以吃的所有东西.
因此,只要我们只关心消耗能源的能力,
消费者[EnergySource]可以替代需要消费者[蔬菜]的地方,

消费者[蔬菜]可以替代需要消费者[竹子]的地方.
因此,消费者[能源]<:消费者[蔬菜]和
消费者[蔬菜]&lt ;:消费者[竹子],即使是关系
类型参数完全相反:

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]

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

定义产品层次结构:

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

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

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

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

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

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

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

最后,MetalBandMember非常专业:get方法保证返回
只有非常具体的金属音乐.

让我们尝试从这三个对象中获取各种娱乐:

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

我们看到所有三个制片人[娱乐],制片人[音乐]和制片人[金属]都可以制作某种娱乐.
我们看到只有制作人[音乐]和制片人[金属]才能保证制作音乐.
最后,我们看到只保证极其专业的制作人[金属]
生产金属而不是别的.因此,可以替换制作人[音乐]和制作人[金属]
制片人[娱乐].制作人[金属]可以代替制作人[音乐].
一般来说,是生产者
可以为不太专业的生产者提供更具体的产品:

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

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

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

相关链接

>类似的讨论?延伸A和? Java 8中的超级B:
Java 8 Comparator comparing() static function
>经典“在我自己的Either实现中flatMap的正确类型参数是什么”问题:Type L appears in contravariant position in Either[L,R]

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读