Scala匿名函数中下划线简化的注意事项

基本使用与原理

在使用匿名函数的时候,如果参数在函数体中只出现溢出,则参数可以省略,并且后面的参数可以使用下划线_开代替,并且下划线必须按照顺序进行接收,使用下划线的时候必须省略参数列表和箭头=>

在Scala中,下划线简化函数扩展为原本函数的过程称为eta-expansion,并且其中存在就近扩展原则:下划线_组成的表达式遇到括号(),或者抵达了最顶层则会发生函数的扩展。这里需要注意,单个的下划线并不算是下划线表达式。

eta-expansion:将简化函数扩展为匿名函数,举例来说,_+1扩展为x=>{x+1}

注意细节

1.多个括号嵌套不用下划线简写

由于下划线表达式遇到括号会发生函数扩展,因此在存在多个括号嵌套的时候可能会发生函数扩展导致的报错。下面举两个常见的错误例子

1
2
3
4
5
6
7
8
9
10
List(1, 2, 3, 4).map(
(_ + 1) * 2
)
// 编译错误,由于 _ + 1 遇到了括号会进行函数扩展,因此map中的参数被扩展为
// x => {x + 1} * 2
// 一个匿名函数和2进行乘法,编译器无法判断乘号在这里的操作,因此报错
// 修改为没有简化的匿名函数
List(1, 2, 3, 4).map(
x => {(x + 1) * 2}
)
1
2
3
4
5
6
7
List(1, 2, 3, 4).foreach(println) // 1.正确
List(1, 2, 3, 4).foreach(println(_)) // 2.正确
List(1, 2, 3, 4).foreach(println(_.toString)) // 3.报错
// 1.正确,其中传入的println是一个函数
// 2.正确,由于单个下划线不是下划线表达式,不会发生函数扩展
// 3.报错,_.toString不是单个下划线,会发生函数扩展,因此扩展后得到的是如下代码
// println(x=>{x.toString}),println接收了一个匿名函数作为参数,但是println不接受该类型的参数,因此报错

2.代码块的返回值和匿名函数混淆

在使用下划线的场景中,我们最终得到的会是很简化的表达,但是这个简化表达往往可能会误导我们,尤其是容易将代码块的返回值和匿名函数混淆。

例如下面的代码:

1
2
3
4
5
val list = List(1, 2, 3, 4).map({
println("-map-")
_ + 1
})
println(list)

我们容易将整个{}代码块中的内容都看作是传入map中的匿名函数的内容。但是实际上,传入map中的参数是一个代码块,而这个代码块的返回值是一个下划线表达式,这个下划线表达式可以扩展为一个匿名函数,因此最终传入map中的函数应该为x=>{x + 1},在获得代码块的返回值的过程中,会执行依次println("-map-"),后面都不会执行了,因此上面代码的输出为

1
2
-map-
List(2, 3, 4, 5)

如果要实现每个元素操作的时候都输出一次-map-,则还是需要使用没有简化的匿名函数

1
2
3
4
5
val list = List(1, 2, 3, 4).map(num => {
println("-map-")
num + 1
})
println(list)

输出结果为

1
2
3
4
5
-map-
-map-
-map-
-map-
List(2, 3, 4, 5)

Scala匿名函数中下划线简化的注意事项
http://example.com/2022/05/10/Scala匿名函数中下划线简化的注意事项/
作者
EverNorif
发布于
2022年5月10日
许可协议