【问题标题】:What are the rules to govern underscore to define anonymous function?管理下划线以定义匿名函数的规则是什么?
【发布时间】:2015-01-24 13:17:59
【问题描述】:

我使用_ 作为创建匿名函数的占位符,问题是我无法预测 Scala 将如何转换我的代码。更准确地说,它错误地确定了我想要的匿名函数有多“大”。

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

使用-Xprint:typer我可以看到Scala将第一个转换为“一个大的匿名函数”:

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

工作的 2th 3th 是 正确 转变为我想要的。

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

为什么会这样?规则是什么?

【问题讨论】:

  • 请注意:您可以更简单地将其编码为val x = List(1,2,3) foreach println

标签: scala lambda anonymous-function partial-application scala-placeholder-syntax


【解决方案1】:

确定下划线范围的简单规则:

  1. 如果下划线是方法的参数,则范围将在该方法之外,否则分别遵循以下规则;
  2. 如果下划线位于由 () 或 {} 分隔的表达式内,则将使用包含下划线的最里面的此类分隔符;
  3. 在所有其他条件相同的情况下,将使用可能的最大表达式。

因此,根据规则 #1,而不是 println((x: Int) => x),范围将被放置在(包括)println 之外。

根据规则#2,后两个示例将具有由括号分隔的函数,因此(x => println(x: Int))

根据规则 #3,第一个示例将是整个表达式,因为没有分隔括号。

【讨论】:

    【解决方案2】:

    我相信 Sobral 先生的回答是不正确的。实际规则可以在Scala Language Reference,第 6.23 节,小标题“匿名函数的占位符语法”中找到。

    唯一的规则是正确包含下划线的innermost表达式定义了匿名函数的范围。这意味着 Sobral 先生的前两条规则是正确的,因为方法调用是一个表达式,给表达式加上括号并不会改变其含义。但第三条规则与事实相反:在所有其他条件相同的情况下,将使用有意义的最小表达式。

    不幸的是,我对 Laskowski 先生在他的第一个示例中观察到的行为的解释有点牵强和投机。当

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

    在 Scala 的 read-eval-print 循环中输入。错误信息是:

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

    如果你稍微改变一下这个例子:

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

    错误信息更容易理解--

    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)
                          ^
    

    为了更好地理解事情,请调用scala,因此:scala -Xprint:parser,在用户键入每个表达式之后,会导致解析器充实的表达式被打印出来。 (还有很多垃圾,我将省略。)对于 Laskowski 的第一个示例,解析器理解的表达式是

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

    对于第二个例子,解析器的版本是

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

    显然,范围规则是在表达式结构完全充实之前应用的。在这两种情况下,解析器都猜测最小的表达式从 List 开始,即使插入括号后就不再正确了。在第二个示例中,除了该假设之外,它还假设因为println 是一个标识符,foreach println 是一个方法链,第一个方法没有参数。然后在println 的错误之前捕获foreach 的错误,将其屏蔽。 println 的错误是它的结果是 Unit,而 foreach 需要一个函数。一旦您看到解析树,就很容易看出这是正确的,但(对我来说)并不清楚为什么解析树是这样的。

    【讨论】: