Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding Numbers together

Tags:

scala

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

like image 920
Andres Avatar asked Jun 18 '11 17:06

Andres


1 Answers

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.

like image 134
Rex Kerr Avatar answered Nov 15 '22 12:11

Rex Kerr