Scala:将Any隐式转换为Numeric

时间:2016-02-02 08:34:21

标签: scala sum tuples implicit-conversion implicit

我正在编写Learning Scala一书的例子,其中一个问题是:

  

如何在所有元组上添加“sum”方法,返回总和   元组中的所有数值?例如,('a',“hi”,2.5,1,   是的.sum应该返回3.5。

我的代码:

AutoCompleteField

我遇到的问题是如何将implicit class PimpedProduct(val p: Product) { def sum = p.productIterator.filter(_.isInstanceOf[Number]).sum } 转换为Any?我可以在每个Numeric[Double]类型上执行match,但这很糟糕。我看了this,这看起来很有帮助,但还不够。

4 个答案:

答案 0 :(得分:2)

您可以使用java.lang.Number进行匹配并转换为double:

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

Scala: checking if an object is Numeric answer中所述,如果给定类型存在某些类型类,则无法检查运行时。

答案 1 :(得分:2)

正如其他答案所示,如果唯一看作“数值”的是Number的实例,这就非常简单了。如果您希望依赖于对Numeric的隐式转换,则会变得更复杂。使用运行时类型查找implicits是疯狂和不可能之间的事情,但我们可以做的是,使用宏在编译时查找implicits。

此宏遵循的方法是确定元组的arity,然后生成用于访问其元素的代码(即x._1,x._2,...)。然后它键入检查这些表达式以确定它们的静态类型。最后,它使用确定的类型来尝试查找隐式,如果成功生成相应的代码,否则它只是忽略该值。

我不得不在反射API中挖掘一下以获得这个不错的结果。我希望现在这是最终的版本......

所以这是宏:

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)"
  }
}

一个小测试程序:

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)
}

注意:如果要编译它,则必须分两个阶段完成:首先是宏,然后是示例。将其逐步粘贴到REPL也可以。

(为Scala 2.11撰写)

答案 2 :(得分:1)

这是一种避免运行时类型检查的方法。它几乎肯定不是你想要的,但是既然你正在试图了解隐含,你可能仍然觉得它很有用......

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

答案 3 :(得分:0)

试试这个:

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