0

I'm working examples from the book Learning Scala and one of the questions asks:

How would you add a “sum” method on all tuples, which returns the sum of all numeric values in a tuple? For example, ('a', "hi", 2.5, 1, true).sum should return 3.5.

My code:

implicit class PimpedProduct(val p: Product) {
    def sum = p.productIterator.filter(_.isInstanceOf[Number]).sum
}

The problem I'm running into is how to convert Any to Numeric[Double]? I could do a match on each Numeric type but that sucks. I read this, which seemed helpful but not quite enough.

Community
  • 1
  • 1
Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219
  • Possible duplicate of [How to pattern-match against every numeric class in one case?](http://stackoverflow.com/questions/19757095/how-to-pattern-match-against-every-numeric-class-in-one-case) – Suma Feb 02 '16 at 09:22
  • Or perhaps a better duplicate: http://stackoverflow.com/questions/12020753/scala-checking-if-an-object-is-numeric – Suma Feb 02 '16 at 09:25

4 Answers4

2

You can use java.lang.Number for matching and conversion to a double:

implicit class TupleSum(val p: Product) {
  def sum = {
    p.productIterator.collect {
      case x: java.lang.Number => x.doubleValue
    }.sum
  }
}

It seems it is not possible to check runtime if some type class exists for given type, as explained in Scala: checking if an object is Numeric answer.

Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191
  • Sorry I commented on the wrong response. My comment was meant for jasoni. I'll delete it and suggest that you do too. Your code is clear and doesn't require any explanation. – Abhijit Sarkar Feb 03 '16 at 01:57
2

As the other answers show, this is pretty simple if the only thing one sees as a "numeric value" are instances of Number. If you instead want to rely on implicit conversions to Numeric, it gets more complicated. Using the runtime type to look up implicits is something between insane and impossible, but what we can do, is, use macros to look up the implicits at compile time.

The approach this macro follows is to determine the arity of the Tuple and then generate code for accessing its elments (i.e. x._1, x._2, ...). Then it type checks these expressions to determine their static type. Finally it uses the determined type to try to look up an implicit, if this succeeds in generates code accordingly, otherwise it just ignores that value.

I had to dig around a bit in the reflection API to get to this nice result. I hope this is now the definitive version...

So here is the macro:

import scala.language.experimental.macros
import scala.language.implicitConversions
import scala.reflect.macros.blackbox.Context

class FancySum(elements: Traversable[Double]) {
  def sum = elements.sum
}

object FancySum {

  implicit def toFancySum(product: Product): FancySum = macro toFancySumImpl

  def toFancySumImpl(c: Context)(product: c.Expr[Product]): c.Tree = {
    import c.universe._

    // Search for Tuple amongst base classes and extract arity
    val tuple = "scala.Tuple([0-9]+)".r
    val arity = product.actualType.baseClasses.map(_.fullName).collectFirst {
      case tuple(c) => c.toInt
    } match {
      case Some(c) => c
      case None => c.abort(c.enclosingPosition, "Not a tupel.")
    }

    val result =  for {
      // for all entries in the tuple
      accessor <- (1 to arity).toList.map(i => {q"""
        ${product.tree}.${TermName("_" + i)}
      """})
      // get the type of that entry
      tpe = c.Expr[Any](c.typecheck(accessor, silent = true)).actualType
      // Find suitable implicit and generate code to convert to Double
      num = c.typecheck(q"""
        import ${c.prefix}._
        implicitly[Numeric[$tpe]].toDouble($accessor)
      """, silent = true)
      r <- num match {
        case EmptyTree => None // if it doesn't typecheck ignore the entry
        case _ => Some(num)
      }
    } yield r

    q"new FancySum($result)"
  }
}

And a small test program:

object FancySumApp extends App {
  import FancySum.toFancySum

  val x= 1
  val foo = (x, "asd", 3)
  println(foo.sum)
  println((0.5, List(), 3, BigInt(2), 10: Any).sum)
  // 5.5, as the type of 10 is forced to Any
  println((1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
      1, 1, 1, 1, 1, 1, 1, 1).sum)
}

Note: if you want to compile it, you have to do it in two stages: the macro first and then the example. Pasting it into REPL step by step works as well.

(Written for Scala 2.11)

dth
  • 2,287
  • 10
  • 17
1

Here's an approach that avoids the runtime type checks. It almost certainly isn't quite what you want, but since you're trying to learn about implicits you might still find it useful...

trait ToDouble[T] {
  def toDouble(x: T): Double
}

trait LowPriorityToDoubleImplicits {
  implicit def defaultToDouble[T]: ToDouble[T] = new ToDouble[T] {
    def toDouble(x: T) = 0.0
  }
}

object ToDoubleImplicits extends LowPriorityToDoubleImplicits {
  implicit def numericToDouble[T](implicit num: Numeric[T]) = new ToDouble[T] {      
    def toDouble(x: T) = num.toDouble(x)
  }
}

implicit class ProductWrapper2[T1, T2](x: Product2[T1, T2])(
  implicit ev1: ToDouble[T1], ev2: ToDouble[T2]) {
  def sum = ev1.toDouble(x._1) + ev2.toDouble(x._2)
}

implicit class ProductWrapper3[T1, T2, T3](x: Product3[T1, T2, T3])(
  implicit ev1: ToDouble[T1], ev2: ToDouble[T2], ev3: ToDouble[T3]) {
  def sum = ev1.toDouble(x._1) + 
            ev2.toDouble(x._2) + 
            ev3.toDouble(x._3)
}

implicit class ProductWrapper4[T1, T2, T3, T4](x: Product4[T1, T2, T3, T4])(
  implicit ev1: ToDouble[T1], ev2: ToDouble[T2], ev3: ToDouble[T3], ev4: ToDouble[T4]) {
  def sum = ev1.toDouble(x._1) +
            ev2.toDouble(x._2) + 
            ev3.toDouble(x._3) + 
            ev4.toDouble(x._4)
}

import ToDoubleImplicits._

(1, "asdf").sum
//1.0

(true, 1.0, BigInt("99999999999999999999999999999999999").sum
//1.0E35

('*', -42, 10.0f, -10L).sum
//0.0
Jason Scott Lenderman
  • 1,908
  • 11
  • 14
  • Thank you for your response. However, I think if you provided descriptions (as comments perhaps) with your code, it'd have been more beneficial. As it stands, I see the code, I think I understand the code, but I fail to see what you had in mind. Yes it uses implicits but so does a lot other code. – Abhijit Sarkar Feb 03 '16 at 01:57
0

Try this:

implicit class PimpedProduct(val p: Product) {
  def sum = p.productIterator.filter(_.isInstanceOf[Number]).map(_.toString.toDouble).sum
}
grzesiekw
  • 477
  • 4
  • 8