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)