1

I am learning Scala right now. I see that specifying type while assigning to new val is not necessary. But then consider the following code:

object MyObject {
  def firstResponse(r: Array[String]): String = r(0)
  def mostFrequent(r: Array[String]): String = { 
    (r groupBy identity mapValues (_.length) maxBy(_._2))._1
  }
  def mostFrequent(r: Array[String], among: Int): String = { mostFrequent(r take among) }

  // throws compile error
  val heuristics = Array(
   firstResponse(_), mostFrequent(_, 3), mostFrequent(_, 4), mostFrequent(_, 5)
  )
}

If I change the last line and specify the type explicitly, then the error is gone

val heuristics: Array[Array[String] => String] = Array(
  firstResponse, mostFrequent(_, 3), mostFrequent(_, 4), mostFrequent(_, 5)
)

What's wrong here?

Edit: As @mdm correctly pointed out,

//This works
val heuristics = Array(firstResponse(_), firstResponse(_))
//This does not work
val heuristics = Array(mostFrequent(_,1), mostFrequent(_,2))

Open question is, why Scala can determine the type of firstResponse(_) correctly while it has difficulty to do the same for mostFrequent(_,1).

sss3
  • 25
  • 4
  • What is the compiler error message? – TeWu Aug 17 '17 at 15:20
  • @TeWu This is the error: `:20: error: missing parameter type for expanded function ((x$4) => mostFrequent(x$4, 3)) firstResponse(_), mostFrequent(_, 3), mostFrequent(_, 4), mostFrequent(_, 5)` You can paste the erroneous code in REPL and check. – sss3 Aug 17 '17 at 16:40

3 Answers3

1

The compiler complains with something similar to this:

Error:(28, 29) missing parameter type for expanded function ((x$3: ) => mostFrequent(x$3, 3))

As you probably already figured out, that happens because the compiler cannot figure out automatically (infer) the type of the input parameter of those functions, when you use _. More precisely, it can't infer the type of mostFrequent(_, 3).

So, if you give the compiler a nudge, either by val heuristics: Array[Array[String] => String] = or by the following:

val heuristics = Array(
    (a : Array[String]) => firstResponse(a),
    (a : Array[String]) => mostFrequent(a, 3),
    (a : Array[String]) => mostFrequent(a, 4),
    (a : Array[String]) => mostFrequent(a, 5)
  )

Things will work as expected.

Looking at posts about _ uses like this or this, you will see that it can mean very many things, depending on the context. In this case I suspect the confusion comes from the fact that you are using _ to transform a call to a method with more than one parameter to an anonymous function.

Notice that both of the following will work fine:

val heuristics = Array(
    firstResponse(_),
    firstResponse(_),
    firstResponse(_)
  )

val heuristics2 = Array(
    firstResponse(_),
    mostFrequent(_: Array[String], 3)
  )

As to the specific reason why a method with more than one argument cannot be transformed into an anonymous function, while one with one argument can, I will delegate to someone with more in-depth knowledge of the compiler's inference mechanics.

mdm
  • 3,928
  • 3
  • 27
  • 43
  • Thanks @mdm ! `val heuristics = Array(firstResponse(_), firstResponse(_))` is an interesting observation indeed. I would still like to hear about how `firstResponse(_)` and `mostFrequent(_, 2)` differ, so will wait for that. – sss3 Aug 17 '17 at 18:33
  • The difference is that `fisrtResponse` has only one parameter, while `mostFrequent` has 2. The compiler can always transform a method with one parameter into a function, but apparently cannot do that "generically" for methods with multiple parameters. As to **why** it can't, I will wait for someone that can clarify how the inference works in details, but for future reference, I think your best option is to use the form `mostFrequent(_:Array[String], 3)`.it's the smallest help you need to give to the compiler. – mdm Aug 17 '17 at 18:48
  • @mdm The problem is not 1 vs 2 parameters, it's overloaded vs non-overloaded. – Alexey Romanov Aug 17 '17 at 21:49
  • @alexey I thought about that, and tried to change the names of the method, so that mostFrequent has one parameter and mostFrequent2 has two. At that point, both methods were not overloaded, and the compiler did infer the one with one parameter and not the one with two. So it must have to do with that: some substitution that can be done automatically for one argument cannot be done automatically for two+ arguments. – mdm Aug 17 '17 at 21:55
  • @mdm Hm. But `mostFrequent(_)` (without removing overloading) doesn't work either. I checked the specification in case it changed in 2.12, but it's as I remembered: `firstResponse(_)` isn't supposed to work. – Alexey Romanov Aug 17 '17 at 22:18
1

Sometimes when you use underscores as placeholders for parameters, the compiler might not have enough information to infer missing parameter types. Therefore, you need to explicitly provide type information. Placeholder syntax act as a “blank” in the expression that needs to be “filled in" and you can fill any value to it. Therefore, compiler will have no information about the type of this placeholder.

val foo = _ + _
//will fail - error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))

The above expression will fail, because compiler will unable to find type of value that fill the placeholder. Therefore, there need to be some way for compiler to know the type. The one way is to provide type information of variable/method explicitly.

val foo: (String, String) => String =  _ + _

The above expression will successfully compiled. Because, compiler resolve type of the parameter from type of variable foo (1st and 2nd placeholder are both as String).

In certain case, compiler can resolve the type from value:

List(1,2,3).foreach(println(_))

In above case, List(1,2,3) is a List of type Int, hence compiler will know type information of placeholder in println(_) as Int which is resolved from value of List.

In addition, you can also provide type of value explicitly in order to let compiler know about type.

val foo =  (_:String) + (_:String) //will return function (String, String) => String

In certain case, if your method have only one parameter, then you don't need to provide explicit type parameter otherwise you need to provide type for placeholder syntax as below:

scala>   def firstResponse(r: Array[String]): String = r(0)
firstResponse: (r: Array[String])String

scala> val foo = firstResponse(_)    //no need to provide type information
foo: Array[String] => String = <function1>

scala>   def firstResponse2(r: Array[String], index:Int): String = r(index)
firstResponse2: (r: Array[String], index: Int)String

scala> val foo = firstResponse2(_, 3)     //will fail, need to provide type information.
<console>:12: error: missing parameter type for expanded function ((x$1) => firstResponse2(x$1, 3))
       val foo = firstResponse2(_, 3)
                                ^
scala> val foo = firstResponse2((_:Array[String]), 3)
foo: Array[String] => String = <function1>

Now coming to your case:

val heuristics = Array(
   firstResponse(_), mostFrequent(_, 3), mostFrequent(_, 4), mostFrequent(_, 5)
)

Here, compiler will have no idea of what is the type because:

  1. val heuristics have no type
  2. Type for placeholder syntax is not explicitly provided.

You have solve the issue by providing type Array[Array[String] => String] to heuristics val as in case 1, and hence compiler compiles it fine.

For case 2, you can modify your code as below:

val heuristics = Array(
   firstResponse(_), mostFrequent(_:Array[String], 3), mostFrequent(_:Array[String], 4), mostFrequent(_:Array[String], 5)
)
Ra Ka
  • 2,995
  • 3
  • 23
  • 31
  • I see that you are pointing towards compiler having difficulty while infering type of _. But, try the following. Define functions `firstResponse` and `mostFrequent` as defined in the question and try `val v = firstResponse(_)` as well as `val v = mostFrequent(_, 3)` . The open question is, why earlier statement compiles and latter does not. – sss3 Aug 17 '17 at 18:39
1

The weird thing is that val foo = firstResponse(_) works, because the specification directly forbids it:

If there is no expected type for the function literal, all formal parameter types Ti must be specified explicitly, and the expected type of e is undefined.

I thought that it could be treated as equivalent to eta-expansion firstResponse _ which worked without expected type because firstResponse isn't overloaded, but it's defined to be the other way around: firstResponse _ means the same as x => firstResponse(x), which is not supposed to work according to the above quote.

So strictly speaking, it appears to be a bug and you should write firstResponse(_: Array[String]) as well.

Though in this case, to avoid repetition I'd provide the expected type as

val heuristics = Array[Array[String] => String](
   firstResponse(_), mostFrequent(_, 3), mostFrequent(_, 4), mostFrequent(_, 5)
)
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487