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 Something
s
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 Something
s 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.