Help with multiple arg lists

What is the correct way to use multiple arglist functions, omitting some of the arglists?

Here is an example. Do I really need to declare the type of limit2 as (Double=>Double, Double) => Double, or can’t I have the compiler figure that out for me?

object Testing {

  import scala.math._

  def limit4(f: Double => Double, dx: Double, dy: Double, a: Double): Double = {
    val f1 = f(a + dx)
    val f2 = f(a + dx / 2)
    if (abs(f1 - f2) < dy)
      f2
    else
      limit4(f, dx / 2, dy, a)
  }
  def limit(dx: Double, dy: Double)(f: Double => Double, a: Double): Double = {
    limit4(f, dx, dy, a)
  }

  val limit2: (Double=>Double, Double) => Double = limit(0.1, 0.0001)

If I omit the type declaration on limit2, I get the following compiler error.

Error:(24, 21) missing argument list for method limit in object Testing
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `limit _` or `limit(_,_)(_,_)` instead of `limit`.
  val limit2 = limit(0.1, 0.0001)

Yes, I can indeed declare limit2 as follows, but that seems to violate the intent of the two arglists. It seems logical that if a function has multiple arglists, then omitting anyone after the first is unambiguous. Right?

  val limit2 = limit(0.1, 0.0001) _   // trailing _ fixes the problem

In my opinion the ability to just trail a _ after an expression to tell the compile “Yes, that’s what I really mean, a function!”, seems like an ugly hack. And it doesn’t really generalize well.

For example, it doesn’t work in the following case

def almostEqual(epsilon:Double)(a:Double,b:Double):Boolean = {
  abs(a-b) < epsilon
}

def limit(f:Double=>Double,dx:Double,test:(Double,Double)=>Boolean)(a:Double):Double = {
  def recur(dx:Double):Double = {
    val f1 = f(a + dx)
    val f2 = f(a + dx / 2)
    if (test(f1,f2))
      f2
    else
      recur(dx / 2)
  }
  recur(dx)
}

val limit2a:(Double=>Double,Double)=>Double = limit(_,0.1, almostEqual(0.0001))(_)

val limit2b = limit(_,0.1, almostEqual(0.0001)) _

This gives the following error:

Error:(24, 21) missing parameter type for expanded function ((x$1: <error>) => (limit(x$1, 0.1, almostEqual(1.0E-4)): (() => <empty>)))
val limit2b = limit(_,0.1, almostEqual(0.0001)) _  // trailing _ doesn't work in this case

Omitting seems to work in Dotty https://scastie.scala-lang.org/ZGrLlV5TTDeJVgvGGyYMTg

def almostEqual(epsilon:Double)(a:Double,b:Double):Boolean = {
  Math.abs(a-b) < epsilon
}

def limit(f:Double=>Double,dx:Double,test:(Double,Double)=>Boolean)(a:Double):Double = {
  ???
}

val limit2b = limit(_,0.1, almostEqual(0.0001)) // works

val limit2c: (Double => Double) => Double => Double = limit2b

@main def main = {
  println(limit2b)
  println(limit2c)
}
2 Likes

Yeah, this was an official Dotty improvement – see http://dotty.epfl.ch/docs/reference/changed-features/eta-expansion.html

2 Likes

I have lots of good impressions about Dotty.