I want javascripts ability to deconstruct in scala. Consider an object in js and a case class in scala. If I want just the first and last attribute from the object/case class then in scala I need to list out all the attributes or I get a pile error.
In scala case class deconstruction (from what I know) is positional using the unapply
method
case class ABC(a: Int, b: Int, c: Int)
val obj = ABC(1,2,3)
// deconstruction
val (a, _, c) = obj
while in javascript its by attribute name
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
Is it possible to use a 'javascript like' object deconstruction in scala? The difference is trivial in this example but with many more attributes that might change order in future revisions, the scala code bees harder to manage.
I want javascripts ability to deconstruct in scala. Consider an object in js and a case class in scala. If I want just the first and last attribute from the object/case class then in scala I need to list out all the attributes or I get a pile error.
In scala case class deconstruction (from what I know) is positional using the unapply
method
case class ABC(a: Int, b: Int, c: Int)
val obj = ABC(1,2,3)
// deconstruction
val (a, _, c) = obj
while in javascript its by attribute name
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
Is it possible to use a 'javascript like' object deconstruction in scala? The difference is trivial in this example but with many more attributes that might change order in future revisions, the scala code bees harder to manage.
Share Improve this question asked Mar 13, 2018 at 19:18 andykaisandykais 1,0842 gold badges14 silver badges28 bronze badges3 Answers
Reset to default 6Correct me if I'm wrong, but as far as I understand it,
const {a, c} = obj
simply brings the values obj.a
and obj.c
under the names a
and c
into scope. If pattern matching is too verbose, you can easily achieve a similar effect using import
, for example:
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
This works also if you have multiple objects of the same class with renaming imports:
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
You can destructure everything that provides an unapply method (extractor):
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
It is automatically generated for case classes so your example allows it as well:
case class ABC(a: Int, b: Int, c: Int)
val obj = ABC(1,2,3)
// deconstruction
val ABC(a, _, c) = obj
Notice that there is a name of object containing extractor - it tells piler where to search for it (as you might have several extractors if you want!).
The same mechanism is used for pattern matching, so when you use:
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)
}
extractors to obtain Option
of a tuple with content - Some mean that matching succeeded and None that it failed.
For collections you might want to look up exact example depending on what do you math against:
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
}
If you insist on using extractors, you can use the idea from this answer
object && {
def unapply[A](a: A): Option[(A, A)] = Some((a, a))
}
and additionally define a separate extractor for each member variable:
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) }
}
Now you can use it in patter matching like this:
val z = Abc(42, 100, 58)
import Abc._
val A(a) && B(b) = z
println(s"a = $a , b = $b")
The output for this example is:
a = 42 , b = 100
Note that it works with an arbitrary number of extractors chained together with &&
, for example:
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")
outputs
3 5 7
as expected. If you really want to work with such unwieldy objects so often, you should consider code generation for the boilerplate code anyway.