Refer to the following code snippet:
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
As开发者_如何学C expected, Orange("red") == Orange("red")
is true
. However, I would like to enforce that only the same type of fruits can be compared, so for instance Orange("red") == Apple("red")
should give an error. Can we enforce this in the signature of ==
in trait Fruit
in an elegant way?
EDIT: I want the error to be caught at compile time, not at runtime.
Scalaz has an Equal "type class" that solves this problem, albeit with a different operator.
https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Equal.scala
The heart of it is basically this (although I use === where they use some 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
}
And is used like so
// 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
Unfortunately, you can't check this statically... At least, not using ==
, which uses Java's Object#equals
method where everything is emphatically defined in terms of raw objects.
If you want type safety, then your only choice is to implement another operator, perhaps something like =|=
, then combine this with type classes to ensure your safety.
I believe that scalaz also has something useful for type-safe equality, but don't know the library well enough to state this for certain.
The other approach you can take, which will only be safe at runtime, is to use the canEqual
pattern as described here. This is already used by case classes and offers a nice way to selectively break the LSP when it's appropriate for equality.
If you are happy to change the name of the method from == to something else we could do this:
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
Then if we compare Apples with Oranges we get:
Apple("red") === Orange("red")
<console>:11: error: type mismatch;
found : Orange
required: Apple
Apple("red") === Orange("red")
and Apples with Apples of the same color we get:
Apple("red") === Apple("red")
res10: Boolean = true
and Apples with Apples of a different color we get:
Apple("green") === Apple("red")
res11: Boolean = false
Try:
trait Fruit {
val color: String
def == (fruit: Fruit) = getClass == fruit.getClass && color == fruit.color
}
精彩评论