Как переопределить Scala generi c методы для определенных c типов? - PullRequest
1 голос
/ 04 мая 2020

У меня сейчас есть что-то вроде этого:

case class Bear(a: String, b: String) {
   val can: Can[T] = ?? 
   def drink[T](str: String) = can.open(str)
}

Мне нужно изменить это, чтобы использовать его только для 4 типов A, B, C и D. Например, учитывая экземпляр Bear, мы должны только сможет звонить bearinstance.drink[A]("abc"), bearinstance.drink[B]("abc"), bearinstance.drink[C]("abc") и bearinstance.drink[D]("abc"). Любой другой тип не должен быть разрешен.

Теперь вопрос в том, как переписать этот метод для определенных c типов?

Другая проблема связана с can, если мне удастся переписать drink для использования с только типы 'A', 'B', 'C' и 'D', мне придется создать can для всех четырех типов в качестве переменных-членов. Как сделать метод generi c для динамического выбора can в зависимости от типа? Один из вариантов - это неявное объявление can вне класса, но для этого необходимо, чтобы были объявлены параметры класса.

Будут оценены любые выводы.

Ответы [ 2 ]

1 голос
/ 04 мая 2020

Тот факт, что вам нужно сделать это, означает, что вы действительно должны рефакторинг вашего кода.

Но в любом случае ...

Попробуйте использовать неявные параметры:

case class Bear(a: String, b: String) {
   val can: Can[T] = ???
   def drink[T](str: String)(implicit ev: CanDrink[T]) = can.open(str)
}

Затем создайте черту CanDrink с неявными экземплярами:

trait CanDrink[T]
implicit object ACanDrink extends CanDrink[A]
implicit object BCanDrink extends CanDrink[B]
//And so on

И теперь вы можете назвать это так:

bearinstance.drink[A]("abc") 
//Gets implicit object ACanDrink

bearinstance.drink[X]("abc") 
//Doesn't work because no implicit parameter given of type CanDrink[X]

В Dotty вы можете попробовать изменить определение напитка, используя Типы объединения, предложенные Дмитрием Митиным:

def drink(x: A | B | C | D)(str: String) = ???
def drink[T](str: String)(using T <:< (A | B | C | D)) = ???

Если вам нужно определить его динамически, используйте ClassTag.

def drink[T](str: String)(implicit ev: ClassTag[T]) = ev match {
  case classOf[A] => ???
  ...
}
0 голосов
/ 04 мая 2020

Если вы на самом деле переопределяете метод generi c, вы должны реализовать его для всех возможных типов параметров типа (в противном случае вы нарушаете контракт класса):

trait BearLike {
  def drink[T](str: String)
}

case class Bear(a: String, b: String) extends BearLike {
  override def drink[T](str: String) = ??? // for all T
}

или

case class Bear(a: String, b: String) {
  def drinkABCD[T](str: String)(implicit can: Can[T]) = can.open(str) // only for A, ..., D
}

или

case class Bear(a: String, b: String) extends BearLike {
  override def drink[T](str: String): Unit = sys.error("Bear is not actually a BearLike")
  def drinkABCD[T](str: String)(implicit can: Can[T]) = can.open(str) // only for A, ..., D
}

при условии, что есть Can[A], ..., Can[D]

trait Can[T] {
  def open(str: String)
}
object Can {
  implicit val a: Can[A] = ???
  implicit val b: Can[B] = ???
  implicit val c: Can[C] = ???
  implicit val d: Can[D] = ???
}

Если вы можете изменить договор, то Вы можете добавить это ограничение (чтобы метод работал только для A, ..., D) к контракту

trait BearLike {
  def drink[T](str: String)(implicit can: Can[T])
}

case class Bear(a: String, b: String) extends BearLike {
  override def drink[T](str: String)(implicit can: Can[T]) = can.open(str)
}

Иногда комбинировать FP (классы типов) с * 1026 нелегко * (наследство). Обычно, если вы начинаете работать с классами типов (Can), вам следует предпочесть классы типов дальше

trait BearLike[B] {
  def drink(str: String)
}

case class Bear[T](a: String, b: String) 
object Bear {
  implicit def bearIsBearLike[T](implicit can: Can[T]): BearLike[Bear[T]] = new BearLike[Bear[T]] {
    override def drink(str: String): Unit = can.open(str)
  }
}
...