类型上限允许子类型但不允许父类型

Upper type bound allowing subtypes but not the parent type

是否可以使用类型绑定的泛型方法达到 "every possible concrete subclass of this trait, but not the trait itself?"

例如,假设我有以下继承层次结构:

sealed trait Fruit

case class Apple() extends Fruit
case class Orange() extends Fruit
...
case class Watermelon() extends Fruit

我想定义一个方法 def eatFruit[T <: ???](fruit: Seq[T]) 允许 TAppleOrangeWatermelon 等类型,但不允许类型 Fruit。类型绑定 [T <: Fruit] 显然不能完成这项工作。

这样做的最初动力是我们有一个 FruitRepository class 允许 batched/bulk 插入不同的水果。批处理是在 class 外部完成的,因此目前它有很多方法,如 saveApples(apples: Seq[Apple])saveOranges(oranges: Seq[Orange]) 等,其中包含大量涉及创建批量更新语句。我想以更通用的方式来管理它,但是任何方法 saveFruit(fruit: Seq[Fruit]) 都允许例如包含存储库无法处理的苹果和橙子的列表。

...我也承认我现在通常很好奇这种类型绑定是否可能,即使我们最终以不同的方式解决存储库问题。

我们可以将上限指令与类型不等式的自定义隐式强制执行相结合。 Taken from here (or generally see: Enforce type difference):

@annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A, B]
object =!= {
  class Impl[A, B]
  object Impl {
    implicit def neq[A, B] : A Impl B = null
    implicit def neqAmbig1[A] : A Impl A = null
    implicit def neqAmbig2[A] : A Impl A = null
  }

  implicit def foo[A, B](implicit e: A Impl B): A =!= B = null
}

然后我们做:

def eatFruit[T <: Fruit](implicit ev: T =!= Fruit) = ???

当我们调用它时:

def main(args: Array[String]): Unit = {
  eatFruit[Fruit]
}

我们得到:

Error:(29, 13) Cannot prove that yuval.tests.FooBar.Fruit =!= yuval.tests.FooBar.Fruit.
    eatFruit[Fruit]

但是这个编译:

eatFruit[Orange]

这里所有的魔力都是由于在 [A, A] 对的范围内造成隐含的歧义,这样编译器就会报错。


我们还可以更进一步,实现我们自己的逻辑类型,比如我们称之为=<:=!=。我们可以稍微改变一下之前的实现:

@annotation.implicitNotFound(msg = "Cannot prove that ${A} =<:=!= ${B}.")
trait =<:=!=[A,B]
object =<:=!= {
  class Impl[A, B]
  object Impl {
    implicit def subtypeneq[B, A <: B] : A Impl B = null
    implicit def subneqAmbig1[A] : A Impl A = null
    implicit def subneqAmbig2[A] : A Impl A = null
  }

  implicit def foo[A, B](implicit e: A Impl B): A =<:=!= B = null
}

现在:

case class Blue()

def main(args: Array[String]): Unit = {
  eatFruit[Fruit] // Doesn't compile
  eatFruit[Blue] // Doesn't compile
  eatFruit[Orange] // Compiles
}