Case for implicits


#1

I’m trying to understand implicits, and I have a situation where I think I’d like to use implicit, but I don’t see how to do it.
As I understand the implicit parameter MUST be the final parameter, and must be its own parameter group. However, in my case I need another parameter group to be the final one so I can use partial evaluation.

Take a look at this example. limit is a function with two parameter groups. If I call limit(math.sin,0.1,almostEqual(0.01)) then I get a function which when given an x will evaluate the limit as sin approaches x to within 1% accuracy. Similarly the function call derivative(math.sin,0.01,almostEqual(0.001)) returns a function which when given x will return the value of the derivative of sin at x to within 0.1% accuracy.

  def limit(f:Double=>Double, delta:Double, test:(Double,Double)=>Boolean)(x:Double):Double = {
    def recur(h:Double, previous:Double):Double = {
      val improved = f(x+h)
      if(test(improved,previous))
        improved
      else
        recur(h/2,improved)
    }
    recur(delta/2,f(x+delta))
  }

  def derivative(f:Double=>Double,delta:Double,test:(Double,Double)=>Boolean)(x:Double) = {

    def estimate(h: Double): Double = {
      (f(x + h) - f(x)) / h
    }
    limit(estimate, delta, test)(0.0)
  }

I’d like to make the test parameter implicit. The best that I can think of is to make it optional (with a default) but not implicit. So I could change the above call to the following limit(math.sin,0.1)() So I could rewrite it as follows. However, that does not allow the caller to define the default test, it only allows the caller to accept my suggested default.

  def limit(f:Double=>Double, delta:Double)(test:(Double,Double)=>Boolean = almostEqual(0.1))(x:Double):Double = {
    def recur(h:Double, previous:Double):Double = {
      val improved = f(x+h)
      if(test(improved,previous))
        improved
      else
        recur(h/2,improved)
    }
    recur(delta/2,f(x+delta))
  }

#2

One possible solution is the following. Change limit to a function which explicitly returns Double=>Double.

  def limit(f:Double=>Double, delta:Double)(implicit test:(Double,Double)=>Boolean):Double=>Double = {
    (x: Double) => {
      def recur(h: Double, previous: Double): Double = {
        val improved = f(x + h)
        if (test(improved, previous))
          improved
        else
          recur(h / 2, improved)
      }
      recur(delta / 2, f(x + delta))
    }
  }

However, then when I call limit(sin,0.1)(Pi/2) the compiler (at least IntelliJ) thinks (Pi/2) is the value of the implicit parameter test rather than the value of x.


#3

Try to assign the function to a variable. Only then apply the value. Note that we can always set the implicit parameters explicitly. When you set the second parameter group, you are in effect setting the implicit parameter explicitly.

  val f = limit(sin,0.1)  // compiler gets a chance to "inject" parameter
  val r = f(Pi/2)

HTHs


#4

or something to the effect of the following also works:

 def limit(f:Double=>Double, delta:Double, x:Double)(implicit test:(Double,Double)=>Boolean):Double = {
    def recur(h: Double, previous: Double): Double = {
      val improved = f(x + h)
      if (test(improved, previous))
        improved
      else
        recur(h / 2, improved)
    }

    recur(delta / 2, f(x + delta))
  }

val f = limit(sin,0.1,_)
val r = f(Pi/2)

I.e., the caller has to curry the function himself.


#5

Yep. More than one way to skin that cat :wink: