当前位置: 首页 > 面试题库 >

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

冯风史
2023-03-14
问题内容

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

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

我对此代码段的理解是Pets是协变的,并且接受属于A的子类型的对象。但是,add函数仅接受类型为A的参数。成为协变意味着Pets可以采用Type A及其子类型的参数。那么这怎么会引发错误。矛盾的问题甚至从何而来。

对以上错误消息的任何解释将非常有帮助。谢谢


问题答案:

TL; DR:

  • 您的Pets类可以通过返回成员变量来产生类型A的值pet,因此aPet[VeryGeneral]不能是它的子类型Pet[VerySpecial],因为当它产生某种东西时VeryGeneral,它不能保证它也是VerySpecial。因此,它不可能是互变的。

  • 您的Pets类可以通过将类型的值作为参数传递给来使用类型A的值add。因此aPet[VerySpecial]不能是pet的子类型Pet[VeryGeneral],因为它会阻塞任何非pet的输入VerySpecial。因此,您的课程不能是协变的。

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

插图:协方差与逆方差:
我将利用这个机会来介绍此漫画的改进版本和更加严格的版本。它说明了 带有子类型和声明站点差异注释的编程语言的协方差和矛盾关系概念(显然,即使Java人员也发现它足够启发人,尽管事实是有关使用站点差异的问题)。

首先,插图:

协方差-反方差漫画

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

协方差的说明(图1的左侧)
考虑以下从非常普遍到非常具体的能源层次:

class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables

现在考虑Consumer[-A]具有单个consume(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!")
}

现在,为什么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

结果是:Fire可以消耗一个GeneralistHerbivore罐头GeneralistHerbivore所消耗的一切,进而可以消耗一个Panda罐头所消耗的一切。因此,只要我们只在乎消耗能源的能力, Consumer[EnergySource]就可以在Consumer[Vegetables]需要a的地方替代,而 Consumer[Vegetables]在Consumer[Bamboo]需要a的地方替代。因此,即使类型参数之间的关系完全相反,也可以认为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]

协方差的说明(图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是最普通的来源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

我们看到这三个Producer[Entertainment]Producer[Music]并且Producer[Metal]可以产生某种Entertainment。我们只看到这一点,Producer[Music]Producer[Metal]保证会产生Music。最后,我们看到只有极专业的人才Producer[Metal]可以保证生产,Metal而没有别的。因此,Producer[Music]与Producer[Metal]可以取代一个Producer[Entertainment]AProducer[Metal]可以代替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]

产品之间的子类型关系与产品生产者之间的子类型关系相同。这就是协方差的含义。



 类似资料:
  • 问题内容: 给定Date有一个名为“ after(Date)”的方法,而Timestamp有一个名为“ after(Timestamp)”的方法,为什么在以下代码中调用Date中的 after 方法? 至于意外结果有人问在这里。 结果 问题答案: 重载是在编译时考虑的;覆盖在执行时考虑。 时间戳 重载 ,它不会 覆盖 现有方法- 因此您只考虑了中的方法; 而且即使你使用它会 仍然 只使用因为编译时

  • 问题内容: 有人告诉我,Java允许协变数组子类型化,换句话说,如果A是B的子类型,那么A []是B []的子类型,但这是一个不好的功能,因为它可能导致运行时错误。有人可以给我一个具体的例子来说明它如何导致运行时错误,以及Java是否/如何解决此问题? 谢谢! 问题答案: 很简单。 只要您将内容取出,协变类型就不错,但是放入内容的那一刻,整个事情就破裂了。假设您有一个采用Object []作为参数

  • 在我的JSP中,我使用JSTL设置某个变量: 然后在jQuery中,在$(文档). Ready(函数(){...})中,我需要检查“myVar”的值。 这不管用。“if”条件的计算结果总是。

  • 问题内容: 我有以下Java代码; 编写if条件的最佳方法是什么? 问题答案: 使用代替: 与常量进行比较避免了必须构造每个执行。 仅供参考,也有常量,为您提供方便。 注意! 您无法使用的原因是它考虑了 比例 : 因此它不适合用于纯粹的数字比较。但是,比较时不考虑规模:

  • 问题内容: 我了解协方差和逆方差。但是有一件小事我无法理解。在Coursera的“ Scala中的函数式编程”课程中,Martin Ordersky提到: 函数的参数类型是互变的,而返回类型是协变的 因此,例如在Java中,让extends出现。并让一个函数为: 我有函数调用为 所以基本上就是这样。根据Wiki,协方差是“从宽到窄转换”。在上面,我们正在从狗变成动物。所以论点类型不是协变而是协变吗

  • 本文向大家介绍如何检查JavaScript变量是否为函数类型?,包括了如何检查JavaScript变量是否为函数类型?的使用技巧和注意事项,需要的朋友参考一下 使用“ typeof”检查它是否是一个函数。您可以尝试运行以下代码来检查JavaScript变量是否为函数类型: 示例