像JavaScript对象一样解构Scala

3

我希望能够在scala中实现类似于javascript的解构功能。比如,在js中有一个对象,而在scala中则有一个case class。如果我只想要对象/ case class 中的第一个和最后一个属性,那么在scala中,我需要列出所有的属性,否则就会出现编译错误。

根据我的了解,scala中的case class解构是基于位置的,使用unapply方法实现。

case class ABC(a: Int, b: Int, c: Int)
val obj = ABC(1,2,3)
// deconstruction
val (a, _, c) = obj

在 JavaScript 中,它是通过属性名称来实现的。

const obj = { a: 1, b: 2, c: 3 }
const { a, c } = obj
// or
const { a: attrA, c: attrC } = obj
// scala deconstruction is essentially javascript array deconstruction
const arr = [1,2,3]
const [ first, _, last ] = arr

是否可能在Scala中使用类似JavaScript的对象解构?在这个例子中差别微不足道,但是随着许多属性在未来版本中可能会改变顺序,Scala代码变得更难管理。

3个回答

6

如果我理解正确,那么就是这样的:

const {a, c} = obj

这段代码将obj.aobj.c的值分别赋给了ac,并将它们引入了作用域。如果模式匹配过于冗长,您也可以使用import轻松实现类似的效果,例如:

case class Abc(a: Int, b: Int, c: Int)
val obj = Abc(1, 2, 3)
val somethingUsingOnlyAandC = {
  import obj.{a, c}
  a + c
}

println(somethingUsingOnlyAandC) // Output: 4

如果您有多个相同类别的对象并重命名了导入,则此方法也适用:

case class Abc(a: Int, b: Int, c: Int)

val x = Abc(1, 2, 3)
val y = Abc(4, 5, 6)

val bar = {
  import x.{a => a1, c => c1}
  import y.{a => a2, c => c2}
  a1 * a2 + c1 * c2
}

println(bar) // prints "22", 1 * 4 + 3 * 6

1
你可以对提供unapply方法(提取器)的所有内容进行解构:
class Test(val a: String, val b: Int)

object Test {

  def unapply(test: Test): Option[(String, Int)] = Some((test.a, test.b))
}

val test = new Test("test", 0)
val (a, b) = test

它是针对 case classes 自动生成的,因此您的示例也允许它:
case class ABC(a: Int, b: Int, c: Int)
val obj = ABC(1,2,3)
// deconstruction
val ABC(a, _, c) = obj

请注意,存在一个包含提取器名称的对象名称 - 这告诉编译器在哪里搜索它(如果您想要多个提取器!)。
相同的机制用于模式匹配,因此当您使用以下内容时:
option match {
  case Some(value) => // uses Some.unapply(option)
  case None        => // uses None.unapply(option)
}

either match {
  case Left(left)   => // uses Left.unapply(either)
  case Right(right) => // uses Right.unapply(either)
}

提取器用于获取带有内容的元组的选项 - 一些意味着匹配成功,而无则表示失败。 对于集合,您可能需要查找确切的示例,具体取决于您要针对什么进行匹配:
list match {
  case v1 :: v2 :: Nil => // 2-element list
  case head :: tail    => // non-empty list
  case Nil             => // empty list
}

seq match {
  case Seq()             => // empty Seq
  case Seq(a, b)         => // 2-element Seq
  case Seq(a, tail @ _*) => destructures into a and iterable tail
}

1
我知道unapply方法在许多地方都被使用,比如match case模式,但这并不能回答我的问题,即如何根据属性名称而不是位置从一个case类中提取属性。 - andykais

1
如果您坚持使用提取器,可以使用 from this answer 的想法。
object && {
  def unapply[A](a: A): Option[(A, A)] = Some((a, a))
}

并且为每个成员变量定义一个单独的提取器:

case class Abc(a: Int, b: Int, c: Int)

object Abc {
  object A { def unapply(x: Abc): Option[Int] = Some(x.a) }
  object B { def unapply(x: Abc): Option[Int] = Some(x.b) }
  object C { def unapply(x: Abc): Option[Int] = Some(x.c) }
}

现在你可以像这样在模式匹配中使用它:
val z = Abc(42, 100, 58)
import Abc._
val A(a) && B(b) = z
println(s"a = $a , b = $b")

这个例子的输出为:
a = 42 , b = 100

请注意,它可以与任意数量的提取器连接在一起使用,例如:&&
case class Abc(a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int)

object Abc {
  object A1 { def unapply(x: Abc): Option[Int] = Some(x.a1) }
  object A2 { def unapply(x: Abc): Option[Int] = Some(x.a2) }
  object A3 { def unapply(x: Abc): Option[Int] = Some(x.a3) }
  object A4 { def unapply(x: Abc): Option[Int] = Some(x.a4) }
  object A5 { def unapply(x: Abc): Option[Int] = Some(x.a5) }
  object A6 { def unapply(x: Abc): Option[Int] = Some(x.a6) }
  object A7 { def unapply(x: Abc): Option[Int] = Some(x.a7) }
  object A8 { def unapply(x: Abc): Option[Int] = Some(x.a8) }

}

object && {
  def unapply[A](a: A): Option[(A, A)] = Some((a, a))
}

val z = Abc(1, 2, 3, 4, 5, 6, 7, 8)
import Abc._
val A3(a3) && A5(a5) && A7(a7) = z
println(s"$a3 $a5 $a7")

输出

3 5 7

如预期的那样。如果您经常需要使用这样笨重的对象,建议考虑为样板代码生成代码。


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