8

我正在使用 Azavea Numeric Sc​​ala 库进行通用数学运算。但是,我不能将它们与 Scala Collections API 一起使用,因为它们需要一个 scala Numeric 并且看起来这两个 Numeric 是互斥的。除了要求所有类型都具有两个 Numeric 的上下文边界之外,有什么方法可以避免在 Scala Collections for Azavea Numeric 上重新实现所有数学运算?

import Predef.{any2stringadd => _, _}

class Numeric {
  def addOne[T: com.azavea.math.Numeric](x: T) {
    import com.azavea.math.EasyImplicits._
    val y = x + 1 // Compiles

    val seq = Seq(x)
    val z = seq.sum // Could not find implicit value for parameter num: Numeric[T]
  }
}

其中 Azavea Numeric 定义为

trait Numeric[@scala.specialized A] extends java.lang.Object with 

com.azavea.math.ConvertableFrom[A] with com.azavea.math.ConvertableTo[A] with scala.ScalaObject {
   def abs(a:A):A
   ...remaining methods redacted...
}

object Numeric {
  implicit object IntIsNumeric extends IntIsNumeric
  implicit object LongIsNumeric extends LongIsNumeric
  implicit object FloatIsNumeric extends FloatIsNumeric
  implicit object DoubleIsNumeric extends DoubleIsNumeric
  implicit object BigIntIsNumeric extends BigIntIsNumeric
  implicit object BigDecimalIsNumeric extends BigDecimalIsNumeric

  def numeric[@specialized(Int, Long, Float, Double) A:Numeric]:Numeric[A] = implicitly[Numeric[A]]
}
4

2 回答 2

4

您可以使用 Régis Jean-Gilles 解决方案,这是一个很好的解决方案,并包装 Azavea 的 Numeric。您也可以尝试自己重新创建方法,但使用 Azavea 的 Numeric。除了 NumericRange,大多数都应该很容易实现。

不过,您可能对Spire感兴趣,它继承了 Azavea 的 Numeric 库。它具有所有相同的功能,但也有一些新功能(更多操作、新数字类型、排序和选择等)。如果您使用的是 2.10(我们的大部分工作都针对 2.10),那么使用 Spire 的 Numeric 几乎可以消除通用方法的所有开销,并且通常运行速度与直接(非通用)实现一样快。

也就是说,我认为您的问题是一个很好的建议;我们真的应该toScalaNumeric在 Numeric 上添加一个方法。您计划使用哪种 Scala 收集方法?Spire 为 Arrays 添加了几个新方法,例如 qsum、qproduct、qnorm(p)、qsort、qselect(k) 等。

于 2012-10-29T14:25:08.977 回答
2

最通用的解决方案是编写一个根据它包装com.azavea.math.Numeric和实现的类scala.math.Numeric

class AzaveaNumericWrapper[T]( implicit val n: com.azavea.math.Numeric[T] ) extends scala.math.Numeric {
  def compare (x: T, y: T): Int = n.compare(x, y)
  def minus (x: T, y: T): T = n.minus(x, y)
  // and so on
}

然后实现一个隐式转换:

// NOTE: in scala 2.10, we could directly declare AzaveaNumericWrapper as an implicit class
implicit def toAzaveaNumericWrapper[T]( implicit n: com.azavea.math.Numeric[T] ) = new AzaveaNumericWrapper( n )

本身是隐式的事实n是这里的关键:它允许在需要隐式类型值的com.azavea.math.Numeric地方自动使用类型的隐式值scala.math.Numeric。请注意,要完整,您可能也想做相反的事情(编写一个类 ScalaNumericWrapper,根据 scala.math.Numeric 实现 com.azavea.math.Numeric)。

现在,上述解决方案有一个缺点:您在每次调用时都会获得一个转换(因此是一个实例化)(到一个具有 type 上下文绑定的方法scala.math.Numeric,并且您只有一个实例com.azavea.math.Numeric在范围内)。因此,您实际上需要为每个数字类型定义 AzaveaNumericWrapper 的隐式单例实例。假设您有类型MyType并且MyOtherType您定义了以下实例com.azavea.math.Numeric

implicit object MyTypeIsNumeric extends AzaveaNumericWrapper[MyType]
implicit object MyOtherTypeIsNumeric extends AzaveaNumericWrapper[MyOtherType]
//...

另外,请记住,azavea 的 Numeric 类的明显主要目的是大大提高执行速度(主要是由于类型参数的专门化)。使用上面的包装器,您会失去专业化,从而失去它的速度。必须一直使用特化,一旦你调用了一个非特化的泛型方法,你就进入了非特化泛型的世界(即使该方法随后回调了一个特化方法)。因此,在速度很重要的情况下,请尝试Numeric直接使用 azavea 而不是 scala Numeric(仅仅因为 AzaveaNumericWrapper 在内部使用它并不意味着您将获得任何速度提升,因为这里不会发生专业化)。

您可能已经注意到,我在示例中避免定义AzaveaNumericWrapperfor typesInt的实例Long等等。这是因为(在标准库中)这些类型的 scala.math.Numeric 的隐式值已经存在。您可能很想隐藏它们(通过类似的东西import scala.math.Numeric.{ShortIsIntegral => _}),以确保使用您自己的(azavea 支持的)版本,但没有意义。我能想到的唯一原因是让它运行得更快,但如上所述,它不会。

于 2012-10-29T14:01:04.510 回答