65

Is there an easy way to convert a case class into a tuple?

I can, of course, easily write boilerplate code to do this, but I mean without the boilerplate.

What I'm really after is a way to easily make a case class lexicographically Ordered. I can achieve the goal for tuples by importing scala.math.Ordering.Implicits._, and voila, my tuples have an Ordering defined for them. But the implicits in scala.math.Ordering don't work for case classes in general.

Douglas
  • 2,555
  • 3
  • 24
  • 30

5 Answers5

97

How about calling unapply().get in the companion object?

case class Foo(foo: String, bar: Int)

val (str, in) = Foo.unapply(Foo("test", 123)).get
// str: String = test
// in: Int = 123
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
S-C
  • 1,909
  • 1
  • 16
  • 14
  • 6
    thanks for the tips, in the REPL 2.9.0-1 I had to remove the () to "get" – Tanjona Nov 11 '11 at 00:32
  • Cool, that worked! With your suggestion I was able to get my case class Ordered like so: `val thisTuple: Ordered[(String, Int)] = Foo.unapply(this).get; val thatTuple = Foo.unapply(that).get; thisTuple compare thatTuple`. That's not the prettiest thing in the world, so I'm still open to suggestions, but your answer surely gets the job done. Thanks! – Douglas Nov 11 '11 at 01:17
  • This is quite cumbersome, not really an improvement over just boilerplating it. – matanster Mar 09 '15 at 11:46
  • 2
    You can't call `get()` in new scala versions, only `get` – Slow Harry Jan 11 '17 at 14:56
  • Calling `.get` on the `Option` returned from .`unapply` is a code smell; i.e. bad practice. So, this answer is fairly sketchy. For a better answer, see this answer where the `.get` is safely and exhaustively handled: https://stackoverflow.com/a/43310062/501113 – chaotic3quilibrium May 04 '19 at 12:11
  • 1
    The method contract of `.unapply` here guarantees that the `Option` will be a `Some` - in other cases it will be `None`. In other cases we might not know, but here we do, and won't have to validate this contract at runtime. Hence it's fine to call `.get`. – ig-dev Jul 23 '19 at 03:26
6

Shapeless will do this for you.

  import shapeless._
  import shapeless.syntax.std.product._

  case class Fnord(a: Int, b: String)

  List(Fnord(1, "z - last"), Fnord(1, "a - first")).sortBy(_.productElements.tupled)

Gets

res0: List[Fnord] = List(Fnord(1,a - first), Fnord(1,z - last))

productElements turns a case class into a Shapeless HList:

scala> Fnord(1, "z - last").productElements
res1: Int :: String :: shapeless.HNil = 1 :: z - last :: HNil

And HLists are converted to tuples with #tupled:

scala> Fnord(1, "z - last").productElements.tupled
res2: (Int, String) = (1,z - last)

Performance is likely to be horrible, since you're constantly converting. You'd probably convert everything to the tupled form, sort that, then convert it back using something like (Fnord.apply _).tupled.

James Moore
  • 8,636
  • 5
  • 71
  • 90
4

Came across this old thread while attempting to do this same thing. I eventually settled on this solution:

case class Foo(foo: String, bar: Int)

val testFoo = Foo("a string", 1)

val (str, in) = testFoo match { case Foo(f, b) => (f, b) }
  • 2
    I like this approach because I can avoid calling `.get` on the `Option` returned from `unapply`. I'm wondering why a case class doesn't have a method called `.toTuple` which returns the literal tuple, not in an `Option`; i.e what you are literally doing. For .`unapply` on a case class instance, the result will always be a 'Some' forcing me to call `.get` which is a code smell. – chaotic3quilibrium May 04 '19 at 12:07
  • 3
    *"I can, of course, easily write boilerplate code to do this, but I mean without the boilerplate"* - this answer does not meet that criteria, and in fact is longer than the simple `(testFoo.foo, testFoo.bar)` – Alex Sep 16 '19 at 10:12
4

Scala 3 has first class support for conversions between case classes and tuples:

scala> case class Foo(foo: String, bar: Int)
// defined case class Foo

scala> Tuple.fromProductTyped(Foo("a string", 1))
val res0: (String, Int) = (a string,1)

scala> summon[deriving.Mirror.ProductOf[Foo]].fromProduct(res0)
val res1: deriving.Mirror.ProductOf[Foo]#MirroredMonoType = Foo(a string,1)
Taig
  • 6,718
  • 4
  • 44
  • 65
3

You might try extending the ProductN trait, for N=1-22, which TupleN extends. It will give you a lot of Tuple semantics, like the _1, _2, etc. methods. Depending on you how you use your types, this might be sufficient without creating an actual Tuple.

Dean Wampler
  • 2,141
  • 13
  • 10
  • I'm not sure how to get this approach to work for me. I want my case class either to be lexicographically Ordered or to have a lexicographical Ordering, without having to write lots of boilerplate. Extending ProductN doesn't seem to work with the implicits in scala.math.Ordering that allow one to easily give tuples a lexicographic ordering. – Douglas Nov 15 '11 at 17:07