What's the best way to extract a value from a scala case class?

10,885

Solution 1

In case classes constructor arguments are vals, so just call:

a.input
b.input

You can also use extractor with the help of unapply method:

val Foo(val1) = a
val Bar(val2) = b

and then use val1 and val2

Update

Then you should use pattern matching on your value:

value match {
  case Foo(val1) => val1
  case Bar(val1) => val1
}

It works just like val Foo(val1) = a, with using generated unapply method (extractor) in your class, and it is also an expression, so you van assign the result to the variable

If you have multiple arguments just change PatMat construct according to the number of your parameters, in your case:

someMethod(a: Something, b: Something) = (a, b) match {
  case (Foo(v1), Foo(v2)) => (v1, v2) // or any other logic with values
  case (Foo(v1), Bar(v2)) => // logic for this case
  ... // logic for other cases
}

The more parameters the more cases you should provide, but you case blank cases if you don't need them

someMethod(a: Something, b: Something) = (a, b) match {
  case (Foo(v1), Foo(v2)) => (v1, v2) // or any other logic with values
  case _ => 
}

in this case all other cases will be ignored, not the best choice, cause the result type will be incorrect. And you also can black values

someMethod(a: Something, b: Something) = (a, b) match {
  case (Foo(v1), _) => v1 // in such case you can work only with v1
  ... // logic for other cases
} 

Solution 2

An alternative to pattern matching could be do redefine your classes like this:

trait Something[T]{
  def input:T
}
case class Foo(input: Int) extends Something[Int]
case class Bar(input: Double) extends Something[Double]

Then, any instance of Something will expose the input property. The only potential downside is that it will be of a generic type when you access it.

Solution 3

The alternative approach

In addition to the direct solution of pattern matching in your method, I'll try to show a somewhat more convoluted, general and functional approach to this kind of situations. Still pattern matching is the most direct and simple answer!


If you can explicitly "certify" in your interface the input accessor, you can generalize how you work with the Something class.

In code this translates to

trait Something[T] {
  def input: T
}

case class Foo(input: Int) extends Something[Int]
case class Bar(input: Double) extends Something[Double]

from here you can define how to "lift" any function you like to one that works over Somethings

Let's say you have methods that takes two inputs (e.g. Ints or Doubles) and you want to operate on such inputs within one of your case classes (i.e. Foo, Bar)

//this function lift your specific input method to one that takes Somethings
def liftSomething2[T, R](f: (T, T) => R): (Something[T], Something[T]) => R =
  (a, b) => f(a.input, b.input)

Let's examine this a bit: it takes a function
(T, T) => R of 2 arguments of type T and a result R

and transforms it in a
(Something[T], Something[T]) => R which takes Somethings as arguments.

Examples

//lifts a function that sums ints
scala> val sumInts = liftSomething2[Int, Int](_ + _)
sumInts: (Something[Int], Something[Int]) => Int = <function2>

//lifts a function that multiplies ints
scala> val multInts = liftSomething2[Int, Int](_ * _)
multInts: (Something[Int], Something[Int]) => Int = <function2>

//lifts a function that divides doubles
scala> val divDbl = liftSomething2[Double, Double](_ / _)
divDbl: (Something[Double], Something[Double]) => Double = <function2>

//Now some test
scala> sumInts(Foo(1), Foo(2))
res2: Int = 3

scala> multInts(Foo(4), Foo(-3))
res3: Int = -12

scala> divDbl(Bar(20.0), Bar(3.0))
res4: Double = 6.666666666666667

//You can even complicate things a bit
scala> val stringApp = liftSomething2[Int, String](_.toString + _)
stringApp: (Something[Int], Something[Int]) => String = <function2>

scala> stringApp(Foo(1), Foo(2))
res5: String = 12

All the above examples lift functions of type (T,T) => R but the "lifting" can be made for all and any argument you need

//This takes three args of different types and returns another type
// the logic doesn't change
def liftSomething3[A,B,C,R](f: (A,B,C) => R): (Something[A], Something[B], Something[C]) => R =
  (a,b,c) => f(a.input, b.input, c.input)

//sums to ints and divides by a double
scala> val sumDiv = liftSomething3[Int,Int,Double,Double]((i,j,d) => (i + j) / d)
sumDiv: (Something[Int], Something[Int], Something[Double]) => Double = <function3>

scala> sumDiv(Foo(5), Foo(30), Bar(4.2))
res7: Double = 8.333333333333332

more...

All we've seen so far should be somewhat related to category theory concepts like Applicative Functors and Comonads, but I'm no expert so I encourage you to search for yourself if you feel this sort of abstractions are useful and interesting.

Solution 4

In your example both the a and b have specific types: Foo and Bar respectively. That's why you can simply access their fields like so:

scala> a.input
res4: Int = 10

scala> b.input
res5: Double = 25.1

If however your value has type Something, then you'll need to pattern-match:

val input = somethingOfTypeSomething match {
  case Foo(input) => input
  case Bar(input) => input
}
Share:
10,885
Electric Coffee
Author by

Electric Coffee

Software Engineer at Valhal Connect My Fields of interest are programming in Java, Scala, F#, Haskell, and Objective-C, FLOSS, POSIX, digital design, and making stuff.

Updated on June 28, 2022

Comments

  • Electric Coffee
    Electric Coffee almost 2 years

    What would be the best and/or easiest way to extract a value that I've saved in a case class?

    take for example the following code:

    abstract class Something
    case class Foo(input: Int) extends Something
    case class Bar(input: Double) extends Something
    
    def someMethod(a: Something, b: Something) {
        // code that extracts values goes here
    }
    
    someMethod(Foo(10), Foo(20))
    someMethod(Bar(2.1), Bar(21.2))
    

    how would I then go about getting the integer or the double itself out of a and b when I call the method like I did under its definition?

    Note that both the parameters are used in the same equation