Несоответствие типов при использовании класса типов

#scala #functional-programming #typeclass

#scala #функциональное программирование #класс типов

Вопрос:

Я определил класс типов следующим образом:

 /** Type class */
trait Drivable[V <: Vehicle]{
    def drive(v:V)
}
  

Он запрашивает подтип транспортного средства в качестве параметра drive функции. И теперь я пытаюсь использовать этот класс типов для Vehicle самого.

 /** ADT */
sealed trait Vehicle{
    def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this)
}
  

компилятор жалуется:

 Error:(15, 74) type mismatch;
 found   : Vehicle.this.type (with underlying type Vehicle)
 required: V
    def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this)
  

Это работает, если я принудительно приведу this к V

 def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this.asInstanceOf[V])
  

Но я ненавижу это делать. Или это также работает, если я просто помещаю это в класс case:

 final case class Car(name:String) extends Vehicle {
    def drive(implicit d: Drivable[Car]): Unit = d.drive(this)
}
  

Но изображений у меня много case classes , я должен повторить это в каждом.

Как правильно использовать этот класс типов в базовом признаке?

Комментарии:

1. Вместо этого добавьте drive в качестве метода расширения , или, если это просто для ADT , тогда в использовании typeclass на самом деле нет смысла. Компилятор прав насчет ошибки, вы говорите, что вам нужен экземпляр Drivable[V] для некоторых, которые еще не указаны V , и вы передаете себя, нет способа доказать и гарантировать, что на сайте вызова this является допустимым экземпляром V ; на самом деле довольно легко создать пример того, как это исправить.

2. Может помочь, если вы сможете описать, какова ваша конечная цель и почему вы решили смоделировать ее таким образом, чтобы мы могли предложить другое решение или объяснить, где была ваша ошибка.

3. У меня их несколько Vehicle , например, я бы создал Car, Motorcycle … и так далее, у каждого есть drive метод, и, конечно, я бы предоставил реализации для каждого из них на сайте, но я не хочу повторять этот вызов в каждом классе case, поэтому я думаю поднять его до trait, каков правильный способ добиться этого?

4. Я действительно не вижу, чем это будет отличаться от удаления класса типов и реализации drive для каждого класса. Или удалите метод drive on Vehicle и оставьте метод расширения выполнять свою работу. Это также зависит от того, как вы хотите использовать свои экземпляры.

5. Да, почему бы просто не использовать обычный способ ООП для выполнения задач?. Нужно Vehicle расширить Drivable , а затем реализовать drive в каждом подклассе. Почему drive нужно принимать транспортное средство в качестве параметра?

Ответ №1:

Либо создайте drive метод расширения

 sealed trait Vehicle

implicit class DrivableOps[V <: Vehicle](val vehicle: V) extends AnyVal {
  def drive(implicit d: Drivable[V]): Unit = d.drive(vehicle)
}
  

или сделайте V членом типа признака, а не параметром типа метода

 sealed trait Vehicle { 
  type V >: this.type <: Vehicle
  def drive(implicit d: Drivable[V]): Unit = d.drive(this)
}

case class Bus() extends Vehicle {
  override type V = Bus
}
case class Car() extends Vehicle {
  override type V = Car
}
  

Тип V может быть сгенерирован автоматически:

 //libraryDependencies  = "com.github.dmytromitin" %% "auxify-macros" % "0.8" 
import com.github.dmytromitin.auxify.macros.self 

@self sealed trait Vehicle { 
  def drive(implicit d: Drivable[Self]): Unit = d.drive(this) 
} 

@self case class Bus() extends Vehicle 
@self case class Car() extends Vehicle
  

Комментарии:

1. Интересно! это работает! однако override выглядит не очень красиво, но теоретически имеет смысл.

2. И implicit один лучше. Спасибо @Dmytro