在Scala中,是否有一种简单的方法将一个case class转换成元组?

67

有没有一种简单的方法将一个case class转换为元组?

当然,我可以轻松地编写样板代码来完成此操作,但我的意思是不使用样板代码。

实际上,我想要的是一种方便地使一个case class按字典顺序排序的方法。我可以通过导入scala.math.Ordering.Implicits._来为元组实现排序,然后我的元组就有了一个定义。但是scala.math.Ordering中的隐式参数通常不适用于所有的case classes。

5个回答

97

尝试在伴生对象中调用unapply().get,怎么样?

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

val (str, in) = Foo.unapply(Foo("test", 123)).get
// str: String = test
// in: Int = 123

6
感谢您的提示,在 REPL 2.9.0-1 中,我需要去掉括号才能“获取”。 - Tanjona
太棒了,成功了!有了你的建议,我能够这样让我的案例类Ordered工作:val thisTuple: Ordered[(String, Int)] = Foo.unapply(this).get; val thatTuple = Foo.unapply(that).get; thisTuple compare thatTuple。虽然这不是世界上最漂亮的东西,但我仍然愿意听取建议,但你的答案肯定能完成任务。谢谢! - Douglas
这相当繁琐,实际上并没有比简单地模板化更好。 - matanster
2
在新版本的Scala中,您不能调用get(),只能使用get - Slow Harry
unapply返回的Option上调用.get是一种代码异味,即不良实践。因此,这个答案相当粗略。有关更好的答案,请参见此答案,在其中安全而彻底地处理了.get:https://dev59.com/-Wsz5IYBdhLWcg3wJUfJ#43310062 - chaotic3quilibrium
1
.unapply 的方法契约保证 Option 将是 Some - 在其他情况下它将是 None。在其他情况下,我们可能不知道,但在这里我们知道,并且不必在运行时验证此契约。因此,调用 .get 是可以的。 - ig-dev

6

Shapeless会为您完成这个操作。

  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)

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

productElements可以将一个case class转换为Shapeless HList:

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

而HLists则通过#tupled转换为元组:

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

性能可能会很差,因为你不断进行转换。你可能会将所有内容转换为元组形式,对其进行排序,然后使用类似于(Fnord.apply _).tupled的方法将其转换回来。


4

Scala 3首次全面支持在case类和元组之间进行转换:

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)

4

我在尝试做同样的事情时,发现了这个旧帖子。最终我选择了这个解决方案:

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
我喜欢这种方法,因为我可以避免在从unapply返回的Option上调用.get。我想知道为什么一个case类没有一个名为.toTuple的方法,它返回字面元组,而不是一个Option;也就是说,你正在做的事情。对于case类实例的.unapply,结果将始终是'Some',迫使我调用.get,这是一种代码味道。 - chaotic3quilibrium
3
我当然可以轻松地编写样板代码来完成这项任务,但我的意思是不用那些样板代码。 - Alex

3

你可以尝试扩展ProductN特质,其中N=1-22,它继承了TupleN。这将为您提供许多元组语义,例如_1_2等方法。根据您使用类型的方式,这可能足以满足您的需求,而无需创建实际的元组。


我不确定如何让这种方法适用于我。我希望我的 case class 要么按字典顺序排序,要么具有字典顺序排序,而无需编写大量样板代码。扩展 ProductN 似乎无法与 scala.math.Ordering 中的隐式一起使用,这些隐式允许轻松地为元组提供字典顺序排序。 - Douglas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接