Как определить все, что расширяет эту черту


См. следующий фрагмент кода:

 trait Fruit {
   val color:String
   def == (fruit:Fruit) = this.color == fruit.color
 }

 case class Orange(color:String) extends Fruit 

 case class Apple(color:String) extends Fruit

Как и ожидалось, Orange("red") == Orange("red") является true. Однако я хотел бы подчеркнуть, что только один и тот же тип фруктов может быть сравнен, поэтому, например, Orange("red") == Apple("red") должен давать ошибку. Можем ли мы обеспечить это в подписи == в черте Fruit элегантным способом?

EDIT: я хочу, чтобы ошибка была поймана во время компиляции, а не во время выполнения.

4 4

4 ответа:

Scalaz имеет равный "класс типа", который решает эту проблему, хотя и с другим оператором.

Https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Equal.scala

Суть его в основном такова (хотя я использую === где они используют некоторые unicode)

/** Defines a type safe === operator */
trait Equals[A] {
 def ===(y : A) : Boolean
}

/** A conventient way to define Equals traits based on the == operator */
def equalA[A](x : A) = new Equals[A] {
  def ===(y : A) = x == y
}

И используется так

// one for oranges
implicit val EqualsOrange = equalA[Orange] _

// one for apples
implicit val EqualsApple = equalA[Apple] _


Orange("red") === Orange("red") // true

Orange("red") === Orange("green") // false

Orange("red") === Apple("red") // Compile error

К сожалению, вы не можете проверить это статически... По крайней мере, не используя ==, который использует метод Java Object#equals, где все подчеркнуто определено в терминах необработанных объектов.

Если вы хотите безопасность типов, то ваш единственный выбор-реализовать другой оператор, возможно, что-то вроде =|=, а затем объединить его с классами типов, чтобы обеспечить вашу безопасность.

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

Другой подход, который вы можете использовать, который будет безопасен только во время выполнения, - это использование шаблона canEqual , как описано здесь. Это уже используется классами case и предлагает хороший способ выборочно разбить LSP, когда это подходит для равенства.

Если вы хотите изменить название метода с == на что-то другое, мы можем сделать следующее:

trait Fruit {
 type FruitType <: Fruit
 val color:String
 def === (fruit:FruitType) = this.color == fruit.color

}


case class Orange(color:String) extends { type FruitType = Orange } with Fruit 

case class Apple(color:String) extends {type FruitType = Apple } with Fruit

Тогда если мы сравним яблоки с апельсинами, то получим:

Apple("red") === Orange("red")
<console>:11: error: type mismatch;
 found   : Orange
 required: Apple
       Apple("red") === Orange("red")

И яблоки с яблоками одного цвета получаем:

Apple("red") === Apple("red")
res10: Boolean = true

И яблоки с яблоками другого цвета получаем:

Apple("green") === Apple("red")
res11: Boolean = false

Попробуйте:

 trait Fruit {
   val color: String
   def == (fruit: Fruit) = getClass == fruit.getClass && color == fruit.color
 }