In Scala, is there an easy way to convert a case class into a tuple?
Asked Answered
C

7

68

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.

Coraliecoraline answered 10/11, 2011 at 23:42 Comment(0)
L
98

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
Logicize answered 11/11, 2011 at 0:8 Comment(6)
thanks for the tips, in the REPL 2.9.0-1 I had to remove the () to "get"Illuminative
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!Coraliecoraline
This is quite cumbersome, not really an improvement over just boilerplating it.Vinegarish
You can't call get() in new scala versions, only getOpportunity
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://mcmap.net/q/292796/-in-scala-is-there-an-easy-way-to-convert-a-case-class-into-a-tupleAdrenalin
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.Endive
B
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.

Briefless answered 5/2, 2018 at 20:3 Comment(0)
E
6

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)
Epigraph answered 16/9, 2021 at 12:36 Comment(0)
B
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) }
Bertle answered 9/4, 2017 at 18:0 Comment(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.Adrenalin
"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)Peculiarize
R
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.

Ransome answered 11/11, 2011 at 17:42 Comment(1)
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.Coraliecoraline
A
1

Well, it's not a general solution, but sadly the most elegant way for me is to add the toTuple inside the case class itself. At least before we migrate to Scala 3.

case class Foo(foo: String, bar: Int) {
  val toTuple: (foo, bar) = (foo, bar)
}
Apeman answered 28/2 at 17:3 Comment(0)
A
0

Specifically for Scala 3, here's a solution I've been using (based on a code snippet from this article):

package org.public_domain

import scala.deriving.Mirror

//Based on code snippet found here:
//  https://taig.medium.com/converting-between-tuples-and-case-classes-in-scala-3-7079ccedf4c0
object TupleUtils:
  def to[A <: Product](value: A)(using
      mirror: Mirror.ProductOf[A]
  ): mirror.MirroredElemTypes = Tuple.fromProductTyped(value)

  def from[A](value: Product)(using
      mirror: Mirror.ProductOf[A],
      ev: value.type <:< mirror.MirroredElemTypes
  ): A = mirror.fromProduct(value)
Adrenalin answered 25/5 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.