akauppi
akauppi

Reputation: 18056

Problems with Scala Numeric[T]

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:

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

Answers (3)

Steve
Steve

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

soc
soc

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

oxbow_lakes
oxbow_lakes

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

Related Questions