8

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 })
AndreasScheinert
  • 1,918
  • 12
  • 18
tiagoboldt
  • 2,426
  • 1
  • 24
  • 30

1 Answers1

5

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.

Moritz
  • 14,144
  • 2
  • 56
  • 55
  • does your first solution works? The compiler will complain that b.value should be a String. – Hugo Sereno Ferreira Oct 14 '11 at 14:13
  • @Moritz, while your second suggestion works, I'm still obligated to insert the type when they differ. That will not work as I won't know in the future what types are arriving at Arithmetic. It should be automatic, just like when you write 2 + 4.1 in REPL and get 6.1. – tiagoboldt Oct 14 '11 at 14:13
  • @Moritz, as for your first suggestion, I was unable to make it work also. the error: ambiguous implicit values: both object BigIntIsIntegral in object Numeric of type object scala.math.Numeric.BigIntIsIntegral and object IntIsIntegral in object Numeric of type object scala.math.Numeric.IntIsIntegral match expected type Numeric[A] val a = new Arithmetic( n1.value, n5.value ), where n1 and n5 are constants. – tiagoboldt Oct 14 '11 at 14:15
  • @HugoSFerreira, @tiagoboldt: if you use the first example you have to provide *all* type parameters to when constructing the instance, e.g.: `new Arithmetic[Double,Int,Double](intConn, dblConn)` as they cannot be inferred. – Moritz Oct 14 '11 at 14:20
  • 1
    @HugoSFerreira did you forget the `import Numeric.Implicits._`? – Moritz Oct 14 '11 at 14:21
  • @Moritz Yes, that was it... It's unfortunate that one needs to specify all the types (after all, the last two can be easily inferred). – Hugo Sereno Ferreira Oct 14 '11 at 14:31
  • @Moritz I can see that I will always have to explicitly specify the type(s) at some point. That really disappointed. I'll accept your answer then, tks :) – tiagoboldt Oct 14 '11 at 17:43