Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting implicit scala Numeric from Azavea Numeric

I am using the Azavea Numeric Scala library for generic maths operations. However, I cannot use these with the Scala Collections API, as they require a scala Numeric and it appears as though the two Numerics are mutually exclusive. Is there any way I can avoid re-implementing all mathematical operations on Scala Collections for Azavea Numeric, apart from requiring all types to have context bounds for both Numerics?

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

Where Azavea Numeric is defined as

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]]
}
like image 611
Woodz Avatar asked Oct 29 '12 11:10

Woodz


2 Answers

You can use Régis Jean-Gilles solution, which is a good one, and wrap Azavea's Numeric. You can also try recreating the methods yourself, but using Azavea's Numeric. Aside from NumericRange, most should be pretty straightforward to implement.

You may be interested in Spire though, which succeeds Azavea's Numeric library. It has all the same features, but some new ones as well (more operations, new number types, sorting & selection, etc.). If you are using 2.10 (most of our work is being directed at 2.10), then using Spire's Numeric eliminates virtually all overhead of a generic approach and often runs as fast as a direct (non-generic) implementation.

That said, I think your question is a good suggestion; we should really add a toScalaNumeric method on Numeric. Which Scala collection methods were you planning on using? Spire adds several new methods to Arrays, such as qsum, qproduct, qnorm(p), qsort, qselect(k), etc.

like image 95
tixxit Avatar answered Oct 24 '22 10:10

tixxit


The most general solution would be to write a class that wraps com.azavea.math.Numeric and implements scala.math.Numeric in terms of it:

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
}

Then implement an implicit conversion:

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

The fact that n is itself an implicit is key here: it allows for implicit values of type com.azavea.math.Numeric to be automatically used where na implicit value of type scala.math.Numeric is expected. Note that to be complete, you'll probably want to do the reverse too (write a class ScalaNumericWrapper that implements com.azavea.math.Numeric in terms of scala.math.Numeric).

Now, there is a disadvantage to the above solution: you get a conversion (and thus an instanciation) on each call (to a method that has a context bound of type scala.math.Numeric, and where you only an instance of com.azavea.math.Numeric is in scope). So you will actually want to define an implicit singleton instance of AzaveaNumericWrapper for each of your numeric type. Assuming that you have types MyType and MyOtherType for which you defined instances of com.azavea.math.Numeric:

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

Also, keep in mind that the apparent main purpose of azavea's Numeric class is to greatly enhance execution speed (mostly due to type parameter specialization). Using the wrapper as above, you lose the specialization and hence the speed that comes out of it. Specialization has to be used all the way down, and as soon as you call a generic method that is not specialized, you enter in the world of unspecialized generics (even if that method then calls back a specialized method). So in cases where speed matters, try to use azavea's Numeric directly instead of scala's Numeric (just because AzaveaNumericWrapper uses it internally does not mean that you will get any speed increase, as specialization won't happen here).

You may have noticed that I avoided in my examples to define instances of AzaveaNumericWrapper for types Int, Long and so on. This is because there are already (in the standard library) implicit values of scala.math.Numeric for these types. You might be tempted to just hide them (via something like import scala.math.Numeric.{ShortIsIntegral => _}), so as to be sure that your own (azavea backed) version is used, but there is no point. The only reason I can think of would be to make it run faster, but as explained above, it wont.

like image 32
Régis Jean-Gilles Avatar answered Oct 24 '22 11:10

Régis Jean-Gilles