0

I'm pattern matching on a tuple of two objects and want to match all cases where object1 is of the same type as object2, something like

(object1, object2) match {
    case (o1: T, o2: T) =>
      // Ignore same objects case
    case
    .
    .
    .
    case (_, _)
      // Here I can be certain that the two object are not of the same type
  
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Or Bar Yaacov
  • 259
  • 3
  • 13
  • 2
    `case (o1, o2) if (o1 == 02) =>` Also, please do not do type checks `case foo: T` is generally unsafe due erasure and usually a code smell. – Luis Miguel Mejía Suárez Sep 03 '20 at 14:19
  • @LuisMiguelMejíaSuárez thanks! how didn't I think of that? You say I should never use something like case a: String or b: Int? can you explain further why? – Or Bar Yaacov Sep 03 '20 at 14:25
  • @LuisMiguelMejíaSuárez Do you check that tuple elements are equal or have equal types? – Dmytro Mitin Sep 03 '20 at 14:28
  • @OrBarYaacov Do you need tuple elements to be equal or have equal types? – Dmytro Mitin Sep 03 '20 at 14:29
  • Because that means you are asking for a subtype, that breaks **Liskov**. It means your design is broken in some way. - Structural match like `case Some(foo)` or `case head :: tail` are not considered subtype checks _(even if under the hood hey are)_. - @DmytroMitin I am checking that both values are equal, that is what I understood from the question. – Luis Miguel Mejía Suárez Sep 03 '20 at 14:33
  • @LuisMiguelMejíaSuárez So for `sealed trait A` `case class B(i: Int) extends A` `case class C(s: String) extends A` you consider `(??? : A) match { case b: B => println(s"i=${b.i}"); case c: C => println(s"s=${c.s}") }` as a code smell but almost equivalent `(??? : A) match { case B(i) => println(s"i=$i"); case C(s) => println(s"s=$s") }` as not a code smell. – Dmytro Mitin Sep 03 '20 at 14:51
  • @DmytroMitin generally speaking yes. There are some cases where the elements in the ADT have so many fields that the _"correct"_ way is too verbose, I haven't had that problem but if you are matching the subclasses of a **ADT** without generics then it is _"fine"_. But an arbitrary type check over any arbitrary non sealed trait is a code smell IMHO - BTW, after reading the question again, I see that I interpreted it wrong, your answer, as always, is great. – Luis Miguel Mejía Suárez Sep 03 '20 at 15:04
  • 1
    @DmytroMitin Actually I'm matching on case objects so this works great, but if there's a solution also for checking equal types I wouldn't mind knowing it :) – Or Bar Yaacov Sep 03 '20 at 15:05
  • @LuisMiguelMejíaSuárez Liskov is a principle from OOP. Breaking Liskov in OOP code would be a sin but would it be ok to break Liskov in FP code? :) – Dmytro Mitin Sep 03 '20 at 15:12
  • 2
    @DmytroMitin **Liskov** is a principle of subtyping. Breaking Liskov in any language with subtyping is a _"sin"_ :p – Luis Miguel Mejía Suárez Sep 03 '20 at 16:15

2 Answers2

3

Try

def test(object1: Any, object2: Any) = (object1, object2) match {
  case (o1, o2) if o1.getClass == o2.getClass => println("same")
  case (_, _) => println("different")
}

test(1, 2) //same
test(1, "2") //different
test(1: Any, "2": Any) //different
test(Some(1), Some("2")) //same

or

import scala.reflect.runtime.universe._
def getType[T: TypeTag](t: T): Type = typeOf[T]

def test[A: TypeTag, B: TypeTag](object1: A, object2: B) = (object1, object2) match {
  case (o1, o2) if getType(o1) == getType(o2) => println("same")
  case (_, _) => println("different")
}

test(1, 2) //same
test(1, "2") //different
test(1: Any, "2": Any) //same
test(Some(1), Some("2")) //different

or

def test[A, B](object1: A, object2: B)(implicit ev: A =:= B) = println("same")

test(1, 2) //compiles
// test(1, "2") //doesn't compile
test(1: Any, "2": Any) //compiles
// test(Some(1), Some("2")) //doesn't compile

If object1 and object2 are actually (case) objects then the easiest is

def test(object1: A, object2: A) = (object1, object2) match {
  case (o1, o2) if o1 == o2 => println("same")
  case (_, _) => println("different")
}

sealed trait A
case object B extends A
case object C extends A

test(B, B) //same
test(B, C) //different
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
2

Another variation to @Dmytro's answer: Instead of TypeTag, you could use ClassTag

import scala.reflect.ClassTag
def test[A: ClassTag, B: ClassTag](object1: A, object2: B): Unit =
  (object1, object2) match {
    case (_: A, _: A) => println("same")
    case (_, _)       => println("different")
  }

test(1, 11)   // same
test(1, "11") // different
Pritam Kadam
  • 2,388
  • 8
  • 16
  • And behavior is different from all `test` in my answer: `test(1, 2)` `test(1, "2")` `test(1: Any, "2": Any)` `test(Some(1), Some("2"))` produce `same` `different` `same` `same` – Dmytro Mitin Sep 03 '20 at 15:35
  • 1
    `Option` isn't higher-kinded. Just because something has a type parameter doesn't mean it's necessarily higher-kinded. – Seth Tisue Sep 03 '20 at 19:56
  • @SethTisue This is a standard dispute (whether "higher" starts from 2 or 1) https://stackoverflow.com/questions/6246719/what-is-a-higher-kinded-type-in-scala – Dmytro Mitin Sep 03 '20 at 21:04
  • @PritamKadam By the way, since you use only `A` in pattern matching the context bound for `B` is not necessary: `def test[A: ClassTag, B](object1: A, object2: B): Unit = ...` – Dmytro Mitin Sep 03 '20 at 21:05