Scala 上限

Scala upper bounds

在典型的 Scala 上限示例中

abstract class Animal {
  def name: String
}

abstract class Pet extends Animal {}

class Cat extends Pet {
  override def name: String = "Cat"
}

class Dog extends Pet {
  override def name: String = "Dog"
}

class Lion extends Animal {
  override def name: String = "Lion"
}

这有什么区别

class PetContainer[P <: Pet](p: P) {
      def pet: P = p
    }

val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)

还有这个?

class PetContainer1(p: Pet) {
      def pet: Pet = p
    }

val dogContainer1 = new PetContainer1(new Dog)
val catContainer1 = new PetContainer1(new Cat)

使用类型上限与直接使用抽象 class/trait 相比有什么优势?

使用上限,您可以拥有特定子类型的集合 - 因此仅限于猫或狗,您可以从 def pet 返回特定子类型。 PetContainer1.

不正确

丢失更准确的类型信息示例:

val doggo: Dog = new Dog
val dogContainer1 = new PetContainer1(doggo)
// the following won't compile
// val getDoggoBack: Dog = dogContainer1.pet  

val dogContainer2 = new PetContainer[Dog](doggo)
// that works
val getDoggoBack: Dog = dogContainer2.pet  

您不能将猫放入狗容器中:

// this will not compile
new PetContainer[Dog](new Cat)

如果你要处理多个元素的集合,它会变得更重要。

两者的主要区别其实就是这两种类型的区别:

def pet: P = p
// and 
def pet: Pet = p

所以,在第一个例子中,pet的类型是P,而在第二个例子中,pet的类型是Pet。换句话说:在第一个示例中,类型 更精确 ,因为它是 Pet.

的特定子类型

如果我在 PetContainer[Dog] 中放入 Dog,我会得到一个 Dog。然而,如果我在 PetContainer1 中放置一个 Dog,我得到一个 Pet,它可以是 CatDog,或者其他完全。 (您还没有创建 类 sealedfinal,因此其他人可以一起创建他们自己的 Pet 子类。)

如果您使用显示表达式类型的编辑器或 IDE,或者您在 REPL 或 Scastie 中尝试您的代码,您实际上会看到不同之处:

dogContainer.pet  // has type `Dog`
catContainer.pet  // has type `Cat`
dogContainer1.pet // has type `Pet`
catContainer1.pet // has type `Pet`

请参阅 Scastie link 示例:

sealed trait Animal: 
  val name: String

sealed trait Pet extends Animal

case object Cat extends Pet:
  override val name = "Cat"

case object Dog extends Pet:
  override val name = "Dog"

case object Lion extends Animal:
  override val name = "Lion"

final case class PetContainer[+P <: Pet](pet: P)

val dogContainer = PetContainer(Dog)
val catContainer = PetContainer(Cat)

final case class PetContainer1(pet: Pet)

val dogContainer1 = PetContainer1(Dog)
val catContainer1 = PetContainer1(Cat)

dogContainer.pet  //=> Dog: Dog
catContainer.pet  //=> Cat: Cat
dogContainer1.pet //=> Dog: Pet
catContainer1.pet //=> Cat: Pet

如果你有一个可以容纳两只宠物的狗舍,这会变得更有趣:

sealed trait Pet:
  val name: String

case object Cat extends Pet:
  override val name = "Cat"

case object Mouse extends Pet:
  override val name = "Mouse"

final case class UnsafePetKennel(pet1: Pet, pet2: Pet)

val unsafeCatKennel       = UnsafePetKennel(Cat, Cat)
val unsafeMouseKennel     = UnsafePetKennel(Mouse, Mouse)
val oopsSomeoneAteMyMouse = UnsafePetKennel(Cat, Mouse)

final case class SafePetKennel[+P <: Pet](pet1: P, pet2: P)

val safeCatKennel   = SafePetKennel[Cat.type](Cat, Cat)
val safeMouseKennel = SafePetKennel[Mouse.type](Mouse, Mouse)
val mouseIsSafe     = SafePetKennel[Cat.type](Cat, Mouse)
// Type error: found `Mouse`, required `Cat`
val mouseStillSafe  = SafePetKennel[Mouse.type](Cat, Mouse)
// Type error: found `Cat`, required `Mouse`

Scastie link

不幸的是,如果我们依赖 Scala 的类型参数推断,事情仍然会出错:

val inferredKennel = SafePetKennel(Cat, Mouse)

有效,因为 Scala 推断 PCatMouse 的最小上限,即 Pet,因此 inferredKennel 有键入 SafePetKennel[Pet],因为 CatMouse 都是 Pet 的子类型,所以这是允许的。

我们可以通过对 SafePetKennel:

稍作修改来解决这个问题
final case class SaferKennel[+P <: Pet, +Q <: Pet](
  pet1: P, pet2: Q)(implicit ev: P =:= Q)

val inferredKennel = SaferKennel(Cat, Mouse)
// Cannot prove that Cat =:= Mouse.

Scastie link