Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

type parameters and numeric widening

As we know, we can add (subtract/multiply/etc.) two numbers of different Numeric types and the result will be the wider of the two types, regardless of their order.

33F + 9L  // Float + Long == Float
33L + 9F  // Long + Float == Float

This is because each of the 7 Numeric classes (Byte, Short, Char, Int, Long, Float, Double) has 7 different +() methods (and -(), *(), etc), one for every Numeric type that can be received as a passed parameter. [There's an extra +() method for handling a String parameter, but that need not concern us here.]

Now consider the following:

implicit class PlusOrMinus[T: Numeric](a: T) {
  import Numeric.Implicits._
  def +-(b: T) = if (util.Random.nextBoolean) a+b else a-b
}

This works if the two operands are the same type, but it also works if the type of the 1st operand is wider than the type of the 2nd.

11F +- 2L  // result: Float = 9.0 or 13.0

I believe what's happening here is that the compiler uses weak conformance to achieve numeric widening on the 2nd operand (the b parameter) as it is passed to the +-() method.

But the 1st operand won't be widened to match the 2nd. It won't even compile.

11L +- 2F  // Error: type mismatch; found: Float(2.0) required: Long

Is there any way around this limitatiion?

I can't use a different type parameter for the b argument (def +-[U: Numeric](b: U) = ...) because a Numeric, expressed via a type parameter, can only add/subtract it's own type.

Is the only solution to create 7 different classes (PlusOrMinusShort/Int/Long/etc.) with 7 different methods (def +-(b:Short), def +-(b:Int), def +-(b:Long), etc.)?

like image 402
jwvh Avatar asked Sep 01 '16 16:09

jwvh


1 Answers

Here is a way:

implicit class PlusOrMinus[T: Numeric](a: T) {
  import Numeric.Implicits._
  def +-(b: T) = plusOrMinus(a,b)
  def +-[U: Numeric](b: U)(implicit ev: T => U) = plusOrMinus[U](a,b)

  private def plusOrMinus[W: Numeric](a: W, b: W): W =
    if (util.Random.nextBoolean) a+b else a-b
}

Then, with this, I get the following interaction:

scala> 11F +- 2L
res0: Float = 9.0

scala> 11L +- 2F
res1: Float = 9.0

The idea is that if I could just have a function plusOrMinus, this whole problem would be trivial, since the same widening could happen for either arguments. After defining such a function, the problem becomes how to embed it into an implicit class to use it in an infix form.

Here, we have only two cases: either the second argument needs to be widened or the argument wrapped by the implicit class needs to be widened. The first of these cases is covered by the first +- method (for the reasons you observed above). For the second, however, we need to explicitly say that there is some conversion that is possible and pass the generic type of the conversion to plusOrMinus.

like image 189
Alec Avatar answered Oct 18 '22 10:10

Alec