Omid
Omid

Reputation: 1989

How to check if types are Tuple in compile time

I have the following case class:

case class MyClass[A,B](a:A, b:B)

I want to add a function like unzip to MyClass so if A and B are Tuple types then I want to extract them like the following:

val item = MyClass[(Int,String), (Int,String)]((2,"two"), (3,"three"))
val item_left = MyClass(item.a._1, item.b._1)
val item_right = MyClass(item.a._2, item.b._2)

How should I do that and check the type for tuple in compile time? I don't want to define it in the companion object and I want it to be a function in MyClass. I know that I can define an implicit function but is it the only way?

Upvotes: 3

Views: 338

Answers (2)

Phasmid
Phasmid

Reputation: 953

Michael's answer is excellent. You could also go a much simpler route if you're willing to require that A and B are sub-types of Product in the declaration of your case class:

case class MyClass[A <: Product, B <: Product](a:A, b:B) {
  def item_left = (a.productIterator.toList(0), b.productIterator.toList(0))
  // etc.
}

Now, you can write:

val x = MyClass((2,"two"), (3,"three"))
x.item_left

which results in:

(2,3)

of type (Any,Any).

I suggest this alternative only because it was unclear to me just how complex you're willing to be. I hope not to elicit any down votes ;)

Upvotes: 0

Michael Zajac
Michael Zajac

Reputation: 55569

You can use the <:< type class to prove that A and B are sub-types of Tuple2, so that you can decompose them. That is, we can write an unzip method to have the some free type parameters that would be the decomposed ordinate types (call them A1, A2, B1, and B2). Then, we require evidence that A <:< (A1, A2) and B <:< (B1, B2). If the sub-typing relationship is true, the compiler will find instances of these type classes, which we can use to finalize the conversion. That is, A <:< (A1, A2) extends the function A => (A1, A2).

case class MyClass[A, B](a: A, b: B) {
  def unzip[A1, A2, B1, B2](implicit
      ev1: A <:< (A1, A2),
      ev2: B <:< (B1, B2)
    ): (MyClass[A1, A2], MyClass[B1, B2]) = {
    val (a1, a2) = ev1(a)
    val (b1, b2) = ev2(b)
    (MyClass(a1, a2), MyClass(b1, b2))
  }
}

In action:

scala> MyClass((2, "two"), (3, "three")).unzip
res6: (MyClass[Int,String], MyClass[Int,String]) = (MyClass(2,two),MyClass(3,three))

For non-tuples:

scala> MyClass(1, 2).unzip
<console>:14: error: Cannot prove that Int <:< (A1, A2).
       MyClass(1, 2).unzip
                     ^

Upvotes: 5

Related Questions