Reputation: 1989
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
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
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