Вопрос

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?

Это было полезно?

Решение

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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top