Reputation: 18056
I have a numeric solving function (Double => Double
) where I tried to be clever and use the Numeric[T]
for keeping the two kinds of numbers apart.
This did not turn out to be easy. The remaining problems are:
Numeric[T]
only has plus, minus etc. operators. implicit evidence$1: Numeric[Double]
functions (see compiler output below)Ideally, I'd like to say, "A
and B
are both Double
, but tell me if I mix them with each other".
Here's the code:
import scala.annotation.tailrec
class Sweep[A: Numeric, B: Numeric]( fDiff: A => B, initialSeed: A, initialStep: A, bEps: B )
{
val anum= evidence$1
val bnum= evidence$2
assert( anum.signum(initialStep) > 0 )
assert( bnum.lt( fDiff(initialSeed), fDiff( anum.plus(initialSeed,initialStep) )) ) // check that it's an increasing function
@tailrec
private def sweep( seed: A, step: A ): A = {
val bDiff= fDiff(seed)
if ( bnum.lt( bnum.abs(bDiff), bEps) ) { // done
seed
} else if ( bnum.signum(bDiff) != anum.signum(step) ) {
sweep( anum.plus(seed,step), step ) // continue, same step and direction ('bDiff' should go smaller)
} else {
val newStep = anum.toDouble(step) / -2.0
sweep( anum.minus(seed,newStep), newStep ) // reverse, smaller step
}
}
// Make sure we take the initial step in the right direction
//
private lazy val stepSign= -bnum.signum( fDiff(initialSeed) )
def apply: A = sweep( initialSeed, stepSign * initialStep )
}
object TestX extends App {
val t= new Sweep( (a: Double) => (a*a)-2, 1.0, 0.5, 1e-3 )()
println( t, math.sqrt(2.0) )
}
I've tried it also with the older (implicit anum: Numeric[A])
parameters, but was unable to have two such (both for A
and B
).
Here's what the compiler says (Scala 2.9):
fsc -deprecation -d out-make -unchecked src/xxx.scala
src/xxx.scala:25: error: type mismatch;
found : newStep.type (with underlying type Double)
required: A
sweep( anum.minus(seed,newStep), newStep ) // reverse, smaller step
^
src/xxx.scala:33: error: overloaded method value * with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (A)
def apply: A = sweep( initialSeed, stepSign * initialStep )
^
src/xxx.scala:38: error: not enough arguments for constructor Sweep: (implicit evidence$1: Numeric[Double], implicit evidence$2: Numeric[Double])Sweep[Double,Double].
Unspecified value parameters evidence$1, evidence$2.
val t= new Sweep( (a: Double) => (a*a)-2, 1.0, 0.5, 1e-3 )()
^
three errors found
Thanks for any ideas.
Upvotes: 3
Views: 476
Reputation: 3068
You want to be working with Fractional
instead of Numeric
. The following compiles for me:
import scala.annotation.tailrec
import math.Fractional.Implicits._
import Ordering.Implicits._
class Sweep[A: Fractional, B: Fractional](fDiff: A => B, initialSeed: A, initialStep: A, bEps: B) {
val aFractional = implicitly[Fractional[A]]
assert(initialStep.signum > 0)
assert(fDiff(initialSeed) < fDiff(initialSeed + initialStep))
@tailrec
private def sweep(seed: A, step: A): A = {
val bDiff = fDiff(seed)
if (bDiff.abs < bEps) {
seed
} else if (bDiff.signum != step.signum) {
sweep(seed + step, step)
} else {
val one = aFractional.one
val newStep = step / aFractional.fromInt(-2)
sweep(seed - newStep, newStep)
}
}
private lazy val stepSign = aFractional.fromInt(-fDiff(initialSeed).signum)
def apply: A = sweep(initialSeed, stepSign * initialStep)
}
val sweep = new Sweep((a: Double) => (a*a)-2, 1.0, 0.5, 1e-3)
println(sweep.apply, math.sqrt(2.0))
Note that to get things like -2.0
in type A
, you'll need to assemble them manually from Fractional.one
or use Fractional.fromInt
.
The other thing worth pointing out is the use of math.Fractional.Implicits
and Ordering.Implicits
which will allow you to use normal math syntax (+, <, /, etc.) instead of calling functions like plus
and div
.
Upvotes: 3
Reputation: 28443
The issue seems to be that you use Double
here ...
val newStep = anum.toDouble(step) / -2.0
... although you want to use Numeric
and actually use it that way in the next line.
For division, have a look at Numeric
's subtypes Integral
and Fractional
.
The compiler doesn't find an implicit evidence, because you explicitly pass none:
new Sweep((a: Double) => (a*a)-2, 1.0, 0.5, 1e-3)()
Removing the explicit empty parameter list fixes that:
new Sweep((a: Double) => (a*a)-2, 1.0, 0.5, 1e-3)
I'm not sure about the requirement of not mixing A
and B
, because you do that in multiple places in your code already.
I'm not sure this is what you want, but the following code works:
import scala.annotation.tailrec
class Sweep[A: Fractional](fDiff: A => A, initialSeed: A, initialStep: A, bEps: A) {
val num = implicitly[Fractional[A]]
assert(num.signum(initialStep) > 0)
assert(num.lt(fDiff(initialSeed), fDiff(num.plus(initialSeed, initialStep)))) // check that it's an increasing function
@tailrec
private def sweep(seed: A, step: A): A = {
val bDiff = fDiff(seed)
if (num.lt(num.abs(bDiff), bEps)) { // done
seed
} else if (num.signum(bDiff) != num.signum(step)) {
sweeimport scala.annotation.tailrec
class Sweep[A: Fractional](fDiff: A => A, initialSeed: A, initialStep: A, bEps: A) {
val num = implicitly[Fractional[A]]
assert(num.signum(initialStep) > 0)
assert(num.lt(fDiff(initialSeed), fDiff(num.plus(initialSeed, initialStep)))) // check that it's an increasing function
@tailrec
private def sweep(seed: A, step: A): A = {
val bDiff = fDiff(seed)
if (num.lt(num.abs(bDiff), bEps)) { // done
seed
} else if (num.signum(bDiff) != num.signum(step)) {
sweep(num.plus(seed, step), step) // continue, same step and direction ('bDiff' should go smaller)
} else {
val newStep = num.div(step, num.fromInt(-2))
sweep(num.minus(seed, newStep), newStep) // reverse, smaller step
}
}
// Make sure we take the initial step in the right direction
private lazy val stepSign = -num.signum(fDiff(initialSeed))
def apply: A = sweep(initialSeed, num.times(num.fromInt(stepSign), initialStep))
}
object TestX extends App {
val t = new Sweep((a: Double) => (a * a) - 2, 1.0, 0.5, 1e-3)
println(t, math.sqrt(2.0))
}
p(num.plus(seed, step), step) // continue, same step and direction ('bDiff' should go smaller)
} else {
val newStep = num.div(step, num.fromInt(-2))
sweep(num.minus(seed, newStep), newStep) // reverse, smaller step
}
}
// Make sure we take the initial step in the right direction
private lazy val stepSign = -num.signum(fDiff(initialSeed))
def apply: A = sweep(initialSeed, num.times(num.fromInt(stepSign), initialStep))
}
object TestX extends App {
val t = new Sweep((a: Double) => (a * a) - 2, 1.0, 0.5, 1e-3)
println(t, math.sqrt(2.0))
}
Upvotes: 0
Reputation: 134310
If you want the compiler to tell you when the type parameters A
and B
are not the same, just use one type parameter:
class Sweep[A: Numeric]( fDiff: A => A, initialSeed: A, initialStep: A, bEps: A )
Upvotes: 0