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.)?
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With