8

I want to define a trait named Ext that renames the existing equals method to equalsByAttributes and defines a new equals method at the same time. The trait is used to extend case classes. My current solution looks somehow hacky:

case class A(id: Int) extends Ext

trait Ext { p: Product =>
    // new implementation
    override def equals(obj: Any) = obj match {
        case that: AnyRef => this eq that
        case _ => false
    }

    // reimplementation of old equals implementation
    def equalsByAttributes(obj: Any) = obj match {
        case that: Product =>
            if (this.getClass.isAssignableFrom(that.getClass) || that.getClass.isAssignableFrom(this.getClass))
                p.productIterator.toList == that.productIterator.toList
            else
                false
        case _ => false
    }
}

I wonder if there is a direct way to reference A's equals method in equalsByAttributes so that one can avoid the reimplementation of this method?

Edit 2012-07-12

Since there is a solution for referencing super implementations with super.METHOD_NAME I thought there must be a similar syntax such as overridden.METHOD_NAME for accessing specific implementations in the base class/trait that is going to be extended by the trait, so that my Ext trait would look like this:

trait Ext { p: Product =>
    override def equals(obj: Any) = ...

    def equalsByAttributes(obj: Any) = overridden.equals(obj)
}
Stefan Endrullis
  • 4,150
  • 2
  • 32
  • 45

3 Answers3

13

Do not change equals on case classes. If you need to do so, do not make your classes case classes. Changing case class methods will make the code behave unexpectedly (that is, unlike case classes), which will increase maintenance cost, break everything that assumes case classes work like case classes, make people's life miserable and get a lot of programmers to hate your guts.

In other words, it's not worth it. Don't do that.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 3
    This is not a problem at all, as long as I don't change the expected behaviour of equals and hasCode. I just want to optimize those methods regarding performance. Thereto I'm automatically generating hidden ids which I compare in `equals` instead of the up to 22 case class arguments. It's just about runtime optimization and it doesn't break anything nor increases maintenance costs. And in my case it's definitely worth to do this optimization because I save a lot of unnecessary comparisons. – Stefan Endrullis Oct 30 '13 at 14:21
  • I had to learn Daniel's advice the hard way. I did learn it. And so, if/when you need to supply the `equals` and `hashCode` methods, I wrote an idiomatic Scala solution in this StackOverflow answer: https://stackoverflow.com/a/56509518/501113 – chaotic3quilibrium Aug 30 '21 at 16:14
7

The compiler will not generate equals (and hashCode, respectively) for case classes that already come with an equals, i.e., that inherit one or declare one themselves. Read more about this in this blog entry. AFAIK the only thing you can do is to implement structural equality by using productIterator provided by the Product trait that case classes extend, just as you did in equalsByAttributes.

Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71
  • You're right. I had an error in reasoning. I somehow thought the equals from the case class must be one level above in the class hierarchy and should be therefore accessible in trait `Ext`. But it's not. It's the same as one would implement equals directly in the (case) class and therefore this equals definition can only override the equals definition of the trait and not the other way around, since super classes and traits cannot cannot reference implementations in sub classes. – Stefan Endrullis Jul 13 '12 at 08:02
  • 2
    BTW, instead of reimplementing the case class functions `equals`, `hashCode` or `toString`, one can use the original definitions of Scala: `ScalaRunTime._equals`, `ScalaRunTime._hashCode` and `ScalaRunTime._toString`. – Stefan Endrullis Aug 14 '12 at 14:21
  • 1
    @StefanEndrullis Like this: https://gist.github.com/2814608? The underscore-prefix makes me slightly nervous, it probably hints at the volatility of this part of the language. Nice idea nevertheless :-) – Malte Schwerhoff Aug 14 '12 at 16:10
0

This code will use the method implementation from the Ext trait:

case class A(id: Int) extends Ext { 
  def someEqual(x:Any) = { 
    super[Ext].equalsByAttributes(x) 
  } 
}

The super[X] here is used to refer to one of the the traits this class extends. This answers your second questions.

For your first question:

trait Ext { p: A => def eql(x:Any) = p.equals(x) }
pedrofurla
  • 12,763
  • 1
  • 38
  • 49
  • 1
    I tested your code. Unfortunately it does not solve the problem. `p.equals(x)` still refers to the equals implementation in `Ext` if `equals` is overridden in `Ext`. – Stefan Endrullis Jul 10 '12 at 07:53