Reputation: 4061
I'me learning scala for the purpose of scientific programing. I'm trying to write some simple generic code using spire. I've got the following example working:
import spire.algebra._
import spire.math._
import spire.implicits._
object TestSqrt {
def testSqrt[T: Numeric](x: T) = {
sqrt(x)
}
def main(args: Array[String]){
val x_d: Double = 4.0
println(testSqrt(x_d))
val x_i: Int = 4
println(testSqrt(x_i))
}
}
This prints out 2.0 and 2, as expected. The problem is that I can't get the same thing to work with the exp function. The following code does not compile:
import spire.algebra._
import spire.math._
import spire.implicits._
object TestExp {
def testExp[T: Numeric](x: T) = {
exp(x)
}
def main(args: Array[String]){
val x_d: Double = 4.0
println(testExp(x_d))
val x_i: Int = 4
println(testExp(x_i))
}
}
The compiler says:
... could not find implicit value for parameter t: spire.algebra.Trig[T]
[error] exp(x)
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Am I doing something wrong, is exp not yet supported, or is this a bug? Can anyone provide a working example of using exp with a generic numeric type from spire?
UPDATE:
I can make exp work by using Trig instead of Numeric, like this:
import spire.algebra._
import spire.math._
import spire.implicits._
object TestExp {
def testExp[T: Trig](x: T) = {
exp(x)
}
def main(args: Array[String]){
val x_d: Double = 1.0
println(testExp(x_d))
val x_f: Float = 1.0f
println(testExp(x_f))
// val x_i: Int = 4
// println(testExp(x_i))
}
}
However, it doesn't work with Int, only floating point types. Further, if I use Trig then I can't use sqrt. The following code does not compile:
}
import spire.algebra._
import spire.math._
import spire.implicits._
object TestSqrt {
def testSqrt[T: Trig](x: T) = {
sqrt(x)
}
def main(args: Array[String]){
val x_d: Double = 4.0
println(testSqrt(x_d))
val x_i: Int = 4
println(testSqrt(x_i))
}
}
And gives the error:
... could not find implicit value for evidence parameter of type spire.algebra.Trig[Int]
[error] println(testSqrt(x_i))
[error] ^
[error] two errors found
[error] (compile:compile) Compilation failed
What should I do if I want both exp and sqrt, and is there a way to make exp work with Integral types?
Upvotes: 1
Views: 615
Reputation: 67300
The type classes are defined to have the same type for the operands and the results of the operations. That is to say, when you run sqrt
on a value of type A
, the result will also be of type A
.
Now you may argue that sqrt
on integers yields integers for some specific input values, such as 4 which you use in your example. On the other hand, what should sqrt(5)
return? It seems it will return the greatest integer smaller than the actual square root:
def testSqrt[T: Numeric](x: T): T = sqrt(x)
scala> testSqrt(5)
res0: Int = 2
This is hardly what you want. Interestingly, when you call the sqrt
function directly, it will return a Double
:
scala> sqrt(5)
res1: Double = 2.23606797749979
The reason is that when you import spire.math._
you get an overloaded sqrt
function:
final def sqrt(x: Double): Double = Math.sqrt(x)
final def sqrt[A](a: A)(implicit ev: NRoot[A]): A = ev.sqrt(a)
This "solves" the problem by giving the first variant priority (as it doesn't require implicit parametes). What happens in your example is that you use a generic signature, therefore the second version of sqrt
will be called. The NRoot
argument is indirectly provided because you say you have a Numeric
.
So you will have to decide what you are going to do—do you want to fall back to Double
when calculating the square root, or go with this rather unconventional idea of potentially truncating the result?
Now to exponentiation. In math
, there are again overloaded versions of exp
, among them:
final def exp(n: Double): Double = Math.exp(n)
final def exp[A](a: A)(implicit t: Trig[A]): A = t.exp(a)
The difference to sqrt
is that you won't get a Trig[Int]
under any circumstances. There is just no meaningful way.
Let's just define a function that can call sqrt
an exp
based on the type classes:
def sqrtAndExp[T: NRoot: Trig](x: T): (T, T) = (sqrt(x), exp(x))
You see you can use more than one context bound in a function declaration. This syntax is equivalent to
def sqrtAndExp[T](x: T)(implicit nroot: NRoot[T], trig: Trig[T]: (T, T) =
(sqrt(x), exp(x))
For doubles, this function works (given you have imported spire.implicits._
):
scala> sqrtAndExp(5.0)
res2: (Double, Double) = (2.23606797749979,148.4131591025766)
But not for integers:
scala> sqrtAndExp(5)
<console>:18: error: could not find implicit value for evidence parameter of type
spire.algebra.Trig[Int]
sqrtAndExp(5)
^
Integers can always be used where doubles are required, however. So you can make it work:
scala> sqrtAndExp[Double](5)
res3: (Double, Double) = (2.23606797749979,148.4131591025766)
Upvotes: 3