Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: generic function multiplying Numerics of different types

I am trying to write a generic weighted average function. I want to relax the requirements on the values and the weights being of the same type. ie, I want to support sequences of say: (value:Float,weight:Int) and (value:Int,weight:Float) arguments and not just: (value:Int,weight:Int)

To do so, I first need to implement a function that takes two generic numerical values and returns their product.

def times[A: Numeric, B: Numeric](x: B, y: A): (A, B) : ??? = {...}

Writing the signature and thinking about the return type, made me realise that I need to define some sort of hierarchy for Numerics to determine the return type. ie x:Float*y:Int=z:Float, x:Float*y:Double=z:Double.

Now, Numeric class defines operations plus, times, etc. only for arguments of the same type. I think I would need to implement a type:

class NumericConverter[Numeirc[A],Numeric[B]]{
type BiggerType=???
}

so that I can write my times function as:

def times[A: Numeric, B: Numeric](x: B, y: A): (A, B) :
NumericConverter[Numeirc[A],Numeric[B]].BiggerType= {...}

and convert the "smaller type" to the "bigger one" and feed it to times().

Am I on the right track? How would I "implement" the BiggerType?

clearly I can't do something like:

type myType = if(...) Int else Float

as that is evaluated dynamically, so it worn't work.

I understand that I might be able to do this Using Scalaz, etc. but this is an academic exercise and I want to understand how to write a function that statically returns a type based on the argument types.

Feel free to let me know if there is a whole easier way of doing this.

update:

this is what I came up with it.

abstract class NumericsConvert[A: Numeric,B: Numeric]{

    def AisBiggerThanB: Boolean

    def timesA=new PartialFunction[(A,B), A] {
        override def isDefinedAt(x: (A, B)): Boolean = AisBiggerThanB
        override def apply(x: (A, B)): A = implicitly[Numeric[A]].times(x._1, x._2.asInstanceOf[A])
    }

    def timesB=new PartialFunction[(A,B), B] {
        override def isDefinedAt(x: (A, B)): Boolean = !AisBiggerThanB
        override def apply(x: (A, B)): B = implicitly[Numeric[B]].times(x._1.asInstanceOf[B], x._2)
    }
    def times: PartialFunction[(A, B), Any] = timesA orElse timesB
}

def times[A: Numeric, B: Numeric](x: B, y: A)= implicitly[NumericsConvert[A,B]].times(x,y)

which is silly as I will have to create implicits for both

IntDouble extends NumericsConvert[Int,Double]

and

DoubleInt extends NumericsConvert[Double,Int]

not to mention that the return type of times is now Any, but regardless, I am getting errors for my times functions. I thought I would add it here in case it might help with arriving at a solution. so side question: how I can pass context bound types of one class/function to another like I am trying to do in times.

like image 355
ShS Avatar asked Apr 12 '17 13:04

ShS


1 Answers

I think you're making this harder than it needs to be.

You need "evidence" that both parameters are Numeric. With that established let the evidence do the work. Scala will employ numeric widening so that the result is the more general of the two received types.

def mult[T](a: T, b: T)(implicit ev:Numeric[T]): T =
  ev.times(a,b)

If you want to get a little fancier you can pull in the required implicits. Then it's a little easier to read and understand.

def mult[T: Numeric](a: T, b: T): T = {
  import Numeric.Implicits._
  a * b
}

Proof:

mult(2.3f , 7)  //res0: Float = 16.1
mult(8, 2.1)    //res1: Double = 16.8
mult(3, 2)      //res2: Int = 6

For more on generic types and numeric widening, this question, and its answer, are worth studying.

like image 170
jwvh Avatar answered Nov 15 '22 17:11

jwvh