我正在编写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,这看起来很有帮助,但还不够。
答案 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
}