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代码进行更详细的描述.
考虑以下能源层次结构,从非常一般到非常具体:
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)
定义产品层次结构:
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)
产品之间的子类型关系与产品生产者之间的子类型关系相同.这就是协方差的意思.
相关链接
关于类似的讨论? extends A和? super B比较:在Java 8
的Java 8 Comparator comparing()静态函数
经典的" flatMap在我自己的Either实现中什么是正确的类型参数"问题:类型L出现在逆变位置Either[L, R]