Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Generics and Numeric Implicits

I need to pass two functions as parameters to a scala function. That function should then evaluate them and get a number from them where it will then operate on. This number can be either a Int, Double or any other numeric type. I would like the function to work, whatever the types it is working with.

The example bellow explains the issue.

import Numeric.Implicits._

class Arithmetic[T : Numeric](val A: Connector[T], val B: Connector[T]) {
  val sum  = new Connector({ A.value + B.value })
}

class Constant[T](var x: T) {
  val value = new Connector({ x })
}

class Connector[T](f: => T) {
  def value: T = f
  override def toString = value.toString()
}

object Main extends App{
  val n1 = new Constant(1)

  // works
  val n5 = new Constant(5)
  val a = new Arithmetic( n1.value, n5.value )
  println(a.sum)

 // no works
 val n55 = new Constant(5.5)
 val b = new Arithmetic( n1.value, n55.value )
 println(b.sum)

}

I've also tried

class Arithmetic[T,R : Numeric](val A: Connector[T], val B: Connector[R]) {

and several other combinations, but I ended up with

error: could not find implicit value for parameter num: scala.math.Numeric[Any]
val sum  = new Connector({ A.value + B.value })
like image 962
tiagoboldt Avatar asked Oct 14 '11 09:10

tiagoboldt


1 Answers

The error message you are seeing is because Numeric[T].plus can only be used to add two values of the same type T. Your code is written under the assumption that numeric widening happens automatically - which will not in this case as the compiler does not know anything about the types except that there exists a Numeric[T] instance.

If you need sum to be a stable value, you will have to provide the necessary type information in the constructor like this:

class Arithmetic[A : Numeric, R <% A, S <% A](val a: Connector[R], b: Connector[S]) {
  val sum = new Connector[A]((a.value:A) + (b.value:A))
}

This requires types R and S to be convertible into some type A for which a Numeric[A] istance is known. When creating an instance you would always have to provide all type parameters as they cannot be inferred.

If you do not need sum to be stable you could change your class to this:

class Arithmetic[A,B](val a: Connector[A], val b: Connector[B]) {

  // if A and B are the same types
  def sum(implicit e: B =:= A, n: Numeric[A]): Connector[A] =
    new Connector(n.plus(a.value, b.value))

  // else widen to C
  def wideSum[C](implicit f: A => C, g: B => C, n: Numeric[C]) =
    new Connector(n.plus(a.value, b.value))
}

val a = new Connector(1)

val b = new Connector(2)

val c = new Connector(3.0)

val d = (new Arithmetic(a,b)).sum

// val e = (new Arithmetic(b,c)).sum // <-- does not compile

val e = (new Arithmetic(b,c)).wideSum[Double]

When widening you will still have to provide the type information though.

like image 166
Moritz Avatar answered Oct 17 '22 08:10

Moritz