2

I have a case class Pair(a: Int, b: Int), which represents a pair of 2 integers. In order to have Pair(2, 5) == Pair(5, 2), I overrided the equals method as follows.

override def equals(that: Any): Boolean = that match {
  case Corner(c, d) => (a == c && b == d) || (a == d && b == c)
  case _ => false
}

Now the equality holds true, Pair(2, 5) == Pair(5, 2) returns true, like I wanted. However, this does not work when pattern matching:

Pair(2, 5) match {
  case Pair(5, 2) => print("This is what I want")
  case _ => print("But this is what I get")
}

Could anyone please assist me? Can/should I even do it this way? What are the alternatives? I really don't want to write case Pair(2, 5) | case(5, 2) => every time I pattern match with pairs.

rusins
  • 309
  • 4
  • 8
  • http://stackoverflow.com/a/25538287/4541415 This answer to a related question is what I ended up using – I highly reccomend anyone looks at this as well. – rusins Jun 28 '16 at 13:27

3 Answers3

5

Short answer:
That just won't work. You could rework your statement like this though:

Pair(2, 5) match {
  case that if that == Pair(5, 2) => println("This is what I want")
  case _ => println("nope")
}

Long answer:
When you match on a case class, it's not using equals; it's actually using the companion object's unapply method. In fact a large majority of the "magic" behind scala's match/case statements boils down to unapply.

When you write case class Pair(a: Int, b: Int), you get a lot of things for free. One of them is an extractor, e.g.:

object Pair {
  def unapply(pair: Pair): Option[(Int, Int)] = {
    Some((pair.a, pair.b))
  }
}

So when you say pair match { case Pair(a, b) => ... }, the compiler thinks Pair.unapply(pair), then assigns a to the value in the _1 position of the resulting tuple, and assigns b to the value in the _2 position. (If Pair.unapply(pair) returned None, that case would fail).

Essentially, you can only get one particular value per input from an extractor, but what you are looking for would require two.

Dylan
  • 13,645
  • 3
  • 40
  • 67
1

I'd say a good solution for this case may be not to override equals, but to disallow unordered pairs instead:

case class Pair(a: Int, b: Int) {
  assert(a <= b, s"First parameter of Pair($a, $b) must be less or equal to the second")
}

object Pair {
  def of(a: Int, b: Int) = if (a <= b) Pair(a, b) else Pair(b, a)
}

Quite unfortunately, Scala doesn't allow "overriding" the apply method in the companion object of a case class, otherwise this would be a very transparent solution.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

As Alexey Romanov stated, it is not possible to override the unapply method of case classes. In my case, however, I don't necessarilly need Pair to be a case class, so first I did this:

class Pair(val a: Int, val b: Int)
object Pair {
  def apply(a: Int, b: Int) = new Pair(a, b)

  def unapply(arg: Pair): Option[(Int, Int)] = {
    if (arg.a < arg.b)
      Some(arg.a, arg.b)
    else
      Some(arg.b, arg.a)
  }
}

Pair(5, 2) match {
  case Pair(3, 4) => "It's dumb"
  case Pair (2, 5) => "Matched!"
  case _ => "Doesn't work"
}

Pair(2, 5) match {
  case Pair(3, 4) => "It's dumb"
  case Pair (2, 5) => "Matched!"
  case _ => "Doesn't work"
}

Pair(2, 5) match {
  case Pair(4, 3) => "It's dumb"
  case Pair(5, 2) => "Doesn't match!"
  case _ => "Should have written the digits in ascending order!"
}

As can be seen, it's not perfect – I still have to make sure to write my match cases with the digits in ascending order. However, once I realised that by simply asserting that the values are ordered in the constructor, I can both make the equals method shorter, and have the apply method take care of the sorting, or just do as Alexey Romanov suggested, which is why I marked his answer as the accepted one.

Community
  • 1
  • 1
rusins
  • 309
  • 4
  • 8
  • Note that a class implementing `equals` also must override `hashCode` in such a way that equal instances always have the same hash code (this applies to your original question as well). `case class` takes care of this consistency automatically (provided all of parameters are themselves correct in this respect). – Alexey Romanov Jun 28 '16 at 13:20