Search code examples
scalafunctional-programmingcurryingpartial-application

What is the mechanism by which functions with multiple parameter lists can (sometimes) be used with less than the required number of parameters?


Let me introduce this question by way of an example. This was taken from Lecture 2.3 of Martin Odersky's Functional Programming course.

I have a function to find fixed points iteratively like so

 object fixed_points {
  println("Welcome to Fixed Points")              
  val tolerance = 0.0001                          
  def isCloseEnough(x: Double, y: Double) =
    abs((x-y)/x) < tolerance                  

  def fixedPoint(f: Double => Double)(firstGuess: Double) = {
    def iterate(guess: Double): Double = {
        println(guess)
        val next = f(guess)
        if (isCloseEnough(guess, next)) next
        else iterate(next)
    }
    iterate(firstGuess)
  }

I can adapt this function to finding square roots like so

  def sqrt(x: Double) =
  fixedPoint(y => x/y)(1.0) 

However, this does not converge for certain arguments (like 4 for example). So I apply an average damping to it, essentially converting it to Newton-Raphson like so

  def sqrt(x: Double) =
  fixedPoint(y => (x/y+y)/2)(1.0) 

which converges.

Now average damping is general enough to warrant its own function, so I refactor my code like so

 def averageDamp(f: Double => Double)(x: Double) = (x+f(x))/2

and

  def sqrtDamp(x: Double) =
    fixedPoint(averageDamp(y=>x/y))(1.0)              (*)

Whoa! What just happened?? I'm using averageDamp with only one parameter (when it was defined with two) and the compiler does not complain!

Now, I understand that I can use partial application like so

 def a = averageDamp(x=>2*x)_                   
   a(3)  // returns 4.5

No problems there. But when I attempt to use averageDamp with less than the requisite number of parameters (as was done in sqrtDamp) like so

 def a = averageDamp(x=>2*x)                (**)

I get an error missing arguments for method averageDamp.

Questions:

  1. How is what I have done in (**) different from (*) that the compiler complains in the former but not the latter?
  2. So it looks like using less than the requisite parameters is allowed under certain circumstances. What are these circumstances and what is the name given to this mechanism? (I realize this would come under the topic of 'currying', but I'm after the specific name of this subset of currying, as it were)

Solution

  • This answer expands on the comment posted by @som-snytt.

    The difference between (**) and (*) is that in the former, fixedPoint provides a type definition, whereas in the latter a does not. Essentially, whenever your code provides an explicit type declaration, the compiler is happy yo overlook the omission of the trailing underscore. This is a deliberate design decision, see Martin Odersky's explanation.

    To illustrate this point, here is a small example.

    object A {
      def add(a: Int)(b:Int): Int = a + b
      val x: Int => Int = add(5) // compiles fine
      val y = add(5) // produces the following compiler error
    }
    /* missing arguments for method add in object A;
       follow this method with `_' if you want to treat it as a partially applied function
       val y = add(5)
                  ^
    */