简体   繁体   中英

How to define anything that extends this trait

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 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
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM