Верхняя граница типа допускает подтипы, но не родительский тип - PullRequest
0 голосов
/ 03 мая 2018

Возможно ли иметь универсальный метод с привязкой типа, которая составляет «каждый возможный конкретный подкласс этой черты, но не саму черту?»

В качестве примера предположим, что у меня есть следующая иерархия наследования:

sealed trait Fruit

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

Я хочу определить метод def eatFruit[T <: ???](fruit: Seq[T]), который позволит T иметь тип Apple, Orange, Watermelon и т. Д., Но не будет иметь тип Fruit. Связанный тип [T <: Fruit], очевидно, не выполняет свою работу.

Первоначальный стимул для этого заключается в том, что у нас есть класс FruitRepository, который позволяет добавлять в пакетные / насыпные продукты различные фрукты. Пакетирование выполняется внешним классом, поэтому на данный момент у него есть много методов, аналогичных saveApples(apples: Seq[Apple]), saveOranges(oranges: Seq[Orange]) и т. Д., Которые содержат много повторяющейся логики, связанной с созданием оператора пакетного обновления. Я хотел бы управлять этим более общим способом, но любой метод saveFruit(fruit: Seq[Fruit]) позволит, например, список, содержащий как яблоки, так и апельсины, который не может обработать хранилище.

... Я также признаю, что теперь мне, как правило, любопытно, возможен ли такой тип привязки типов, даже если мы решим проблему с хранилищем другим способом.

1 Ответ

0 голосов
/ 03 мая 2018

Мы можем комбинировать директиву верхней границы с пользовательским неявным применением неравенства типов. Взятый отсюда (или вообще см .: Принудительная разница типов ):

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