I have a List[Number]. It contains values of different subclasses of Number - Int, Double, and so on. I want to sum all the values together. If the list only contains Int, I'd like the result to be an Int. If there are mixes, I'd like to follow the normal Scala rules: Int + Double = Double ( and so on ).
I've tried this:
val result = list.reduceLeft(_ + _)
This is not even compilable. Number has no + method defined that takes another Number.
How can I accomplish this?
Andrés
Copying my answer from the Scala mailing list where the question was first asked:
Number
is a Java class, and has no functionality aside from converting itself to primitive types. Unfortunately, Scala's AnyVal
doesn't do anything aside from mark the primitive types, and Numeric[T]
from Scala only knows about its own type, not a mix of types.
So you're left with fairly unpleasant pattern matching; the shortest syntax if you are sure you only have to handle double and int is:
list.reduceLeft((l,r) => (l: Any, r: Any) match {
case (li: Int, ri: Int) => li + ri
case _ => l.doubleValue + r.doubleValue
})
Note that I'm casting to Any
to get the Scala pattern match to do the unboxing for us; if you leave it as Number, you have to pattern match on java.lang.Integer
and then valueOf
it, which is a pain.
If you want more you'll have to build in the appropriate promotions:
list.reduceLeft((l,r) => (l: Any) match {
case li: Int => (r: Any) match {
case ri: Int => li + ri
case rf: Float => li + rf
case rl: Long => li + rl
case _ => li + r.doubleValue
}
case lf: Float => /* etc. */
})
If you have to do this more than a little, you're probably better off abandoning Numeric
in favor of your own wrapper classes that know how to add themselves together with appropriate promotion; then you can write the pattern matching just once and return to using + in your list comprehensions.
sealed abstract class Addable {
def +(a: Addable): Addable
}
final case class MyInt(value: Int) extends Addable {
def +(a: Addable) = a match {
case MyInt(i) => MyInt(value + i)
case MyFloat(f) => MyFloat(value + f)
...
}
}
final case class MyFloat(value: Float) extends Addable {
def +(a: Addable) = a match {
case MyInt(i) => MyFloat(value + i)
...
}
}
...
One slight advantage of this method is that you can decide to promote values a different way than the standard (for example, you might decide that float + long = double, since a float really isn't cut out to keep much of long's numeric precision, and do MyDouble(value.toDouble + f.toDouble)
, for instance.
It's all rather awkward; neither Java nor Scala really provide support for mixed-type mathematics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With