What are the rules to govern underscore to define

2020-01-29 08:00发布

问题:

I am using _ as placeholder for creating anonymous function, and the problem is I cannot predict how Scala is going to transform my code. More precisely, it mistakenly determines how "large" the anonymous function I want.

 List(1,2,3) foreach println(_:Int)   //error !
 List(1,2,3) foreach (println(_:Int)) //work
 List(1,2,3) foreach(println(_:Int))  //work

Using -Xprint:typer I can see Scala transforms the first one into "a big anonymous function":

x$1 => List(1,2,3) foreach(println(x$1:Int))

the worked 2th 3th are right transformation into what I want.

... foreach (x$1 => println(x$1:Int)) 

Why this? What's the rule ?

回答1:

Simple rules to determine the scope of underscore:

  1. If the underscore is an argument to a method, then the scope will be outside that method, otherwise respective the rules below;
  2. If the underscore is inside an expression delimited by () or {}, the innermost such delimiter that contains the underscore will be used;
  3. All other things being equal, the largest expression possible will be used.

So, by the rule #1, instead of println((x: Int) => x), the scope will be placed outside (including) println.

By rule #2, the latter two examples will have the function delimited by parenthesis, so (x => println(x: Int)).

By rule #3, the first example will be the whole expression, as there are no delimiting parenthesis.



回答2:

I believe Mr. Sobral's answer is incorrect. The actual rules can be found in Scala Language Reference, section 6.23, subhead "Placeholder Syntax for Anonymous Functions."

The only rule is that the innermost expression that properly contains the underscore defines the scope of the anonymous function. That means that Mr. Sobral's first two rules are correct, because a method call is an expression and parenthesizing an expression doesn't change its meaning. But the third rule is the opposite of the truth: all other things being equal, the smallest expression that makes sense will be used.

Unfortunately, my explanation for the behavior Mr. Laskowski observed for his first example is a bit involved and speculative. When

List(1,2,3) foreach println(_:Int)

is typed at the Scala read-eval-print loop. The error message is:

error: type mismatch;
 found   : Unit
 required: Int => ?
              List(1,2,3) foreach println(_:Int)
                                         ^

If you vary the example a tiny bit:

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

the error message is easier to make sense of --

error: missing arguments for method foreach in class List;
follow this method with `_' if you want to treat it as a partially applied function
          List(1,2,3).foreach println(_:Int)
                      ^

To understand things a little better, call scala thus: scala -Xprint:parser, which, after every expression is typed by the user, causes the expression as fleshed out by the parser to be printed. (Along with a lot of garbage, which I'll omit.) For Laskowski's first example, the expression understood by the parser is

((x$1: Int) => List(1, 2, 3).foreach(println((x$1: Int))))

For the second example, the parser's version is

((x$1: Int) => List(1, 2, 3).foreach.println((x$1: Int)))

Apparently the scope rule is applied before the expression structure has been fully fleshed out. In both cases, the parser guesses that the smallest expression starts at List, even though once the parens are inserted that's no longer true. In the second example, in addition to that assumption it assumes that, because println is an identifier, foreach println is a chain of methods, the first having no arguments. The error at foreach is then caught before the error at println, masking it. The error at println is that its result is Unit, and foreach requires a function. Once you see the parse tree, it's easy to see that this is correct, but it's not clear (to me) why the parse tree is what it is.