[Solved] Compare two numbers


I think the main problem is the conversion of numeric types. So let´s encode that:

trait NumericConversion[X, Y] {
  def convert(x: X): Y
}

Of course one have to specify that abstract concept: (for example)

implicit object Int2IntNumericConversion extends NumericConversion[Int, Int] {
  def convert(i: Int): Int = i
}
implicit object Double2DoubleNumericConversion extends NumericConversion[Double, Double] {
  def convert(d: Double): Double = d
}
implicit object Int2DoubleNumericConversion extends NumericConversion[Int, Double] {
  def convert(i: Int): Double = i.toDouble
}

Now the comparing method goes as follows:

def compareTwoNumbers1[N1, N2, N3](n1: N1, n2: N2)
                                  (implicit conv1: NumericConversion[N1, N3], 
                                            conv2: NumericConversion[N2, N3], 
                                              ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

Usage:

compareTwoNumbers1[Int, Double, Double](3, 8D)  // -1

What a pitty, we have to explicitly state the type parameters, so I tried:

def compareTwoNumbers2[N3] = new {
  def apply[N1, N2](n1: N1, n2: N2)(implicit conv1: NumericConversion[N1, N3],
                                             conv2: NumericConversion[N2, N3], 
                                               ord: Ordering[N3]): Int = {
    ord compare (conv1 convert n1, conv2 convert n2)
  }
}

That reduces to one type argument:

compareTwoNumbers2[Double](3, 8D)  // -1

Not satisfying, so I tried this:

trait NumericUpperBound[Num1, Num2, UpperBound]
implicit object NumericUpperBoundIDD extends NumericUpperBound[Int, Double, Double]
implicit object NumericUpperBoundDID extends NumericUpperBound[Double, Int, Double]

With a new comparing method:

def compareTwoNumbers3[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: NumericConversion[N1, N3], 
                                         conv2: NumericConversion[N2, N3], 
                                           ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

Now it works:

compareTwoNumbers3(3, 8D)  // -1

Of course, type classes for all primitives must be created. But it´s flexible to extend it to BigInt, etc. later on.

EDIT

The comment by @wvxvw which mentions a matrix of NumericUpperBounds inspired me to circumvent a matrix, here is a running example (excluding Byte and Short for the moment):

trait ==>[X, Y] extends (X => Y)

object ==> {
  def apply[X, Y](f: X => Y): X ==> Y = {
    new (X ==> Y) {
      def apply(x: X): Y = f(x)
    }
  }
}

implicit val Int2LongNumericConversion = ==> { x: Int => x.toLong }
implicit val Int2FloatNumericConversion = ==> { x: Int => x.toFloat }
implicit val Int2DoubleNumericConversion = ==> { x: Int => x.toDouble }
implicit val Long2FloatNumericConversion = ==> { x: Long => x.toFloat }
implicit val Long2DoubleNumericConversion = ==> { x: Long => x.toDouble }
implicit val Float2DoubleNumericConversion = ==> { x: Float => x.toDouble }
implicit def reflexiveNumericConversion[X]: X ==> X = new (X ==> X) { def apply(x: X): X = x }

trait NumericUpperBound[Num1, Num2, UpperBound]

implicit def reflexiveNumericUpperBound[X]: NumericUpperBound[X, X, X] = new NumericUpperBound[X, X, X] {}
implicit def inductiveNumericUpperBound1[X, Y](implicit ev: X ==> Y): NumericUpperBound[Y, X, Y] = new NumericUpperBound[Y, X, Y] {}
implicit def inductiveNumericUpperBound2[X, Y](implicit ev: X ==> Y): NumericUpperBound[X, Y, Y] = new NumericUpperBound[X, Y, Y] {}

def compareTwoNumbers[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: N1 ==> N3, 
                                         conv2: N2 ==> N3, 
                                           ord: Ordering[N3]): Int = {
  ord compare (n1, n2)
}

compareTwoNumbers(9L, 13) // -1

2

solved Compare two numbers