Как определить все, что расширяет эту черту
См. следующий фрагмент кода:
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 ответа:
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
К сожалению, вы не можете проверить это статически... По крайней мере, не используя
==
, который использует метод JavaObject#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