-1

What is it that makes the following code produce a compile error in Scala 2.11?

class Foo {
  def fn(a: Seq[Int], b: Int => Int) {}

  // Comment this line out
  def fn(a: Map[String, String], c: String => Double) {}
}

object Bar {
  def main(args: Array[String]) {
    val f = new Foo()
    f.fn(Seq(1, 2), _ * 2)
  }
}

Error:

Error:(9, 21) missing parameter type for expanded function ((x$1) => x$1.$times(2))
    f.fn(Seq(1, 2), _ * 2)
                    ^

but yet the code compiles if the Map[String, String], String => Double version of fn is commented out. I can easily work round this by giving the compiler a hint or two, but I never like having to explicitly specify types in Scala :-)

(Aside: I'm aware method overloading is frowned on in Scala)

Community
  • 1
  • 1
Philip Kendall
  • 4,304
  • 1
  • 23
  • 42

2 Answers2

3

You're creating some kind of a “chicken and egg” problem for the compiler:

  1. To choose an overload the compiler needs to know the full type of all arguments.
  2. To obtain the full type for the function argument in your call, the compiler needs to infer the type of its argument.
  3. To infer the type of this argument, however, it needs to go back to 1. and pick an overload (the right hand side of * isn't sufficient for type inference because * itself could be overloaded itself).

The compiler can't break out of this circle, and consequently bails out. You have to show the compiler the door:

  • If you specify the type of the lambda argument you solve 2. and enable the compiler to infer the proper type of the function and thus pick the proper overload.
  • If you use a curried function—as suggested in the other answer—you solve 1. and help the compiler pick the proper overload based on the first argument so that it can subsequently infer the type of the second argument.
  • The main point seems to be (1). It looks as if the compiler should be able to choose the first `fn` definition, because the type of the first argument is `Seq[Int]`, but the point is that it doesn't rule out that you want it to coerce `Seq[Int]` to be `Map[String, String]`. Why can't it rule out the coercion? – expz Jan 28 '18 at 04:09
  • 1
    @expz It could, I presume but as said it uses the full signature to resolve overloads. It can't resolve overloads with partial signatures as in this case, even if—in this particular special case—the partial signature unambiguously identifies the overload. –  Jan 29 '18 at 07:48
2

I think the compiler can not figure out which overload to choose, because the type of the function is not fully specified. One common way to assist the scala compiler in type inference is to use multiple parameter blocks, like:

class Foo {
  def fn(a: Seq[Int])(b: Int => Int) {}
  def fn(a: Map[String, String])(c: String => Double) {}
}

object Bar extends App {
  val f = new Foo()
  f.fn(Seq(1, 2))(_ * 2) // this works because the overload is chosen based on the first parameter list
}

If you don't want to do this, you have to specify the type of the function more explicitly, like this:

f.fn(Seq(1,2), (x: Int) => x * 2)
Chirlo
  • 5,989
  • 1
  • 29
  • 45
Rüdiger Klaehn
  • 12,445
  • 3
  • 41
  • 57