我了解Scala中的协变和逆变。协变在现实世界中有许多应用,但是我无法想到任何逆变的应用实例,除了函数的那些老套例子。
能否有人举出一些使用逆变的真实世界例子?
我了解Scala中的协变和逆变。协变在现实世界中有许多应用,但是我无法想到任何逆变的应用实例,除了函数的那些老套例子。
能否有人举出一些使用逆变的真实世界例子?
Function
之外最简单的两个例子是排序和相等性。然而,在Scala标准库中,第一个不是协变的,第二个甚至不存在。因此,我将使用Scalaz的等效物:Order 和Equal。
接下来,我需要一些类层次结构,最好是熟悉的,并且这两个概念都必须对它有意义。如果Scala有一个所有数字类型的超级类Number
,那就太完美了。不幸的是,它没有这样的东西。
所以我要尝试用集合来做这些例子。为了简单起见,让我们只考虑Seq[Int]
和List[Int]
。显然,List[Int]
是Seq[Int]
的子类型,即List[Int]<:Seq[Int]
。
那么,我们可以用它做什么呢?首先,让我们编写一个比较两个列表的东西:
def smaller(a: List[Int], b: List[Int])(implicit ord: Order[List[Int]]) =
if (ord.order(a,b) == LT) a else b
现在我打算为Seq[Int]
编写一个隐式的Order
:
implicit val seqOrder = new Order[Seq[Int]] {
def order(a: Seq[Int], b: Seq[Int]) =
if (a.size < b.size) LT
else if (b.size < a.size) GT
else EQ
}
有了这些定义,我现在可以像这样做:
scala> smaller(List(1), List(1, 2, 3))
res0: List[Int] = List(1)
请注意,我正在请求一个Order[List[Int]]
,但我传递了一个Order[Seq[Int]]
。这意味着Order[Seq[Int]] <: Order[List[Int]]
。鉴于Seq[Int] >: List[Int]
,这只有因为逆变才可能。
下一个问题是:这有什么意义吗?
让我们再考虑一下smaller
。我想比较两个整数列表。自然地,任何比较两个列表的方法都可以接受,但是将两个Seq[Int]
进行比较的方法的逻辑是什么呢?
请注意在seqOrder
的定义中,正在比较的内容成为它的参数。显然,一个List[Int]
可以作为一个期望Seq[Int]
的参数。由此得出,比较Seq[Int]
的方法可以替换比较List[Int]
的方法:它们都可以与相同的参数一起使用。
那反过来呢?假设我有一个仅比较::
(列表的cons)的方法,它和Nil
一起是List
的子类型。很明显,我不能使用它,因为smaller
可能会收到一个Nil
进行比较。由此得出,Order[::[Int]]
不能替换Order[List[Int]]
。
让我们继续讨论相等性,并编写一个方法:
def equalLists(a: List[Int], b: List[Int])(implicit eq: Equal[List[Int]]) = eq.equal(a, b)
由于Order
扩展了Equal
,因此我可以使用相同的隐式(implicit)如下:
scala> equalLists(List(4, 5, 6), List(1, 2, 3)) // we are comparing lengths!
res3: Boolean = true
这里的逻辑是相同的。任何可以确定两个Seq[Int]
是否相同的东西,显然也可以确定两个List[Int]
是否相同。由此可知,Equal[Seq[Int]] <: Equal[List[Int]]
,这是正确的,因为Equal
是反变。
def smaller(a: List[Int], b: List[Int])(implicit ord: Order[Seq[Int]]) = if (ord.order(a,b) == LT) a else b
?而不是使用 Order[List[Int]]
来达到相同的目的吗?为什么要使用逆变? - Chao这个例子来自我之前工作的最后一个项目。假设你有一个类型类 PrettyPrinter[A]
为类型为 A
的对象提供漂亮打印的逻辑。如果 B >: A
(即如果 B
是 A
的超类) 并且你知道如何漂亮地打印 B
(即有一个可用的 PrettyPrinter[B]
实例),那么你可以使用相同的逻辑来漂亮地打印 A
。换句话说,B >: A
意味着 PrettyPrinter[B] <: PrettyPrinter[A]
。因此,你可以在 A
上将 PrettyPrinter[A]
声明为逆变的。
scala> trait Animal
defined trait Animal
scala> case class Dog(name: String) extends Animal
defined class Dog
scala> trait PrettyPrinter[-A] {
| def pprint(a: A): String
| }
defined trait PrettyPrinter
scala> def pprint[A](a: A)(implicit p: PrettyPrinter[A]) = p.pprint(a)
pprint: [A](a: A)(implicit p: PrettyPrinter[A])String
scala> implicit object AnimalPrettyPrinter extends PrettyPrinter[Animal] {
| def pprint(a: Animal) = "[Animal : %s]" format (a)
| }
defined module AnimalPrettyPrinter
scala> pprint(Dog("Tom"))
res159: String = [Animal : Dog(Tom)]
一些其他的例子包括来自Scala标准库的Equal
, Show
(与上面的PrettyPrinter
同构),以及Scalaz中的Resource
类型类。
编辑:
正如Daniel指出的,Scala的Ordering
不是逆变的。(我真的不知道为什么。)您可以考虑使用scalaz.Order
,它旨在与scala.Ordering
具有相同的目的,但其类型参数是逆变的。
补充:
超类型-子类型关系只是两种类型之间可能存在的一种关系类型。可能存在许多这样的关系。假设有两种类型A
和B
,它们通过函数f: B => A
(即任意关系)相关联。如果您可以为数据类型F[_]
定义一个操作contramap
,该操作可以将类型为B => A
的函数提升到F[A => B]
,则称数据类型F[_]
是逆变函子。
需要满足以下法律:
x.contramap(identity)
== x
x.contramap(f).contramap(g)
== x.contramap(f compose g)
上面讨论的所有数据类型(Show
, Equal
等)都是逆变函子。这个属性使我们能够执行有用的操作,例如下面所示的操作:
假设您定义了一个类Candidate
:
case class Candidate(name: String, age: Int)
您需要一个按照候选人年龄排序的 Order[Candidate]
。现在您知道有一个可用的 Order[Int]
实例。 您可以使用 contramap
操作从中获取一个 Order[Candidate]
实例:
val byAgeOrder: Order[Candidate] =
implicitly[Order[Int]] contramap ((_: Candidate).age)
trait Event
trait UserEvent extends Event
trait SystemEvent extends Event
trait ApplicationEvent extends SystemEvent
trait ErrorEvent extends ApplicationEvent
trait Sink[-In] {
def notify(o: In)
}
-
符号标记类型参数,Sink类型变为逆变。def appEventFired(e: ApplicationEvent, s: Sink[ApplicationEvent]): Unit = {
// do some processing related to the event
// notify the event sink
s.notify(e)
}
def errorEventFired(e: ErrorEvent, s: Sink[ErrorEvent]): Unit = {
// do some processing related to the event
// notify the event sink
s.notify(e)
}
几个假设的Sink实现。
trait SystemEventSink extends Sink[SystemEvent]
val ses = new SystemEventSink {
override def notify(o: SystemEvent): Unit = ???
}
trait GenericEventSink extends Sink[Event]
val ges = new GenericEventSink {
override def notify(o: Event): Unit = ???
}
appEventFired(new ApplicationEvent {}, ses)
errorEventFired(new ErrorEvent {}, ges)
appEventFired(new ApplicationEvent {}, ges)
Sink[ApplicationEvent]
的方法来调用一个 Sink[SystemEvent]
甚至是一个 Sink[Event]
。同样,您也可以使用一个 Sink[Event]
来调用期望 Sink[ErrorEvent]
的方法。
通过将不变性替换为逆变性约束,Sink[SystemEvent]
成为 Sink[ApplicationEvent]
的子类型。因此,逆变性也可以被视为一种“扩展”关系,因为类型从更具体的类型“扩展”到更通用的类型。
结论
这个例子已经在一系列有关 我的博客 上的变异的文章中进行了描述。
最后,我认为理解其背后的理论也是有帮助的...
Animal
和 Cat
,Cat
继承自 Animal
。现在,假设您有一个类型为 Printer[Cat]
的对象,其中包含打印 Cat
的功能。你有一个像这样的方法:
def print(p: Printer[Cat], cat: Cat) = p.print(cat)
但问题是,由于 Cat
是一个 Animal
,所以应该也能使用 Printer[Animal]
打印 Cat
,对吗?Printer[T]
定义为像 Printer[-T]
这样的逆变形式,那么我们可以将 Printer[Animal]
传递给上述的 print
函数,并使用它的功能来打印 Cat
。IComparer
类,同样也是逆变的。为什么?因为我们应该能够使用 Animal
比较器来比较 Cat
。