【问题标题】:Upper type bound allowing subtypes but not the parent type上限类型限制允许子类型但不允许父类型
【发布时间】:2018-10-13 21:41:28
【问题描述】:

是否可以有一个泛型方法,其类型绑定相当于“此特征的每个可能的具体子类,但不是特征本身?”

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

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 类,它允许批量/批量插入不同的水果。批处理是在类外部完成的,因此目前它有很多类似于saveApples(apples: Seq[Apple])saveOranges(oranges: Seq[Orange]) 等的方法,其中包含很多涉及创建批处理更新语句的重复逻辑。我想以更通用的方式管理它,但任何方法 saveFruit(fruit: Seq[Fruit]) 都允许例如包含苹果和橙子的列表,存储库无法处理。

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

【问题讨论】:

    标签: scala generics polymorphism type-bounds


    【解决方案1】:

    我们可以将上限指令与类型不等式的自定义隐式强制结合起来。 Taken from here(或一般见: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] 对的范围内创建了隐含的歧义,以至于编译器会抱怨。


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

    @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
    }
    

    【讨论】:

    • 好吧,使用模棱两可的隐式来强制类型不等式是某种形式的邪恶天才。也就是说,虽然这回答了我的问题,但我想指出,这仅适用于我指定泛型类型 - 在我的示例中,如果我编写 eatFruit(List(Apple(), Orange()) 并让编译器解析泛型类型,它仍然可以编译。不知道为什么 - 也许与两者的共同超类型真的是Fruit with Product with Serializable这一事实有关?
    • 刚刚验证了这一点 - 如果您有一个所有继承对象都是案例类的密封特征,您还需要为 Fruit with Product with Serializable 添加一个隐式 =!= 参数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2021-01-22
    • 1970-01-01
    • 2013-07-23
    相关资源
    最近更新 更多