在Scala中,可以将函数当做一个参数传入方法中调用。假设有如下一个方法:
def sayHelloBeforeAction(action:Unit=>Unit){
println("Hello!")
action()
}
该方法接受一个类型为Unit=>Unit
的函数action
为参数,在方法中调用此函数:action()
。这看起来并没有什么问题,虽然也能编译通过,但是却会报告一个警告:
warning: Adaptation of argument list by inserting () is deprecated: this is unlikely to be what you want.
signature: Function1.apply(v1: T1): R
given arguments: <none>
after adaptation: Function1((): Unit)
action()
^
然而,如果action
接受至少一个参数,则不会出现此警告,这是为什么呢?
通过查阅StackOverflow的问题: Adaptation of argument list by inserting () has been deprecated,其高票答案给出了如下解释:
Unit
类型自动推断在Scala 2.11时就被废弃了,因为它可能引发一些令人疑惑的行为。
考虑如下代码:
class Foo[T](value: T)
val x = new Foo
这显然无法通过编译,因为调用的构造方法需要一个类型为T
的参数,但是却没有给出。然而令人惊讶的是,在scala 2.10.4之前这是可以编译通过的,没有任何错误和警告。
这是因为编译器自动推断出了Unit
类型的参数,所以它会将这段代码替换为:
val x = new Foo[Unit](()) // Foo[Unit]
正如这个新引进的警告信息所言,这可能不是你想要的结果。
另一个比较出名的例子如下:
scala> List(1,2,3).toSet()
// res1: Boolean = false
调用toSet()
本应该是一个编译时错误,因为toSet
是无参方法(译注:在Scala中如果定义不带括号的无参方法,那么调用时也不能加括号)。但是编译器会竭尽所能地使它通过编译,最终上面的代码被翻译成:
scala> List(1,2,3).toSet.apply(())
这就意味着:检测()
是否属于该集合。显然是否定的,所以上面的代码返回了res1: Boolean = false
。(译注:实际上,上面代码可以看作是两步:首先,val set=List(1,2,3).toSet
,这将得到:set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
。并且,由于集合Set
类有一个apply
方法:def apply(elem: A)
,其作用是检测elem
是否属于该集合,所以set()
就被拓展成set.apply()
即:set.apply(())
。)
所以,从Scala 2.11开始,如果要将()
当做Unit
类型参数传入,就必须显式的写出。
因此,由于action
是Unit=>Unit
类型的,使用时不要写成action()
而应该显式地写成action(())
。
下面一个问题是,如何使用这个sayHelloBeforeAction
函数,考虑下面代码:
sayHelloBeforeAction{
println("Tom")
}
报的错误是:
<console>:14: error: type mismatch;
found : Unit
required: Unit => Unit
println("Tom")
^
如果改成:
sayHelloBeforeAction{
()=>println("Tom")
}
仍然会报错:
<console>:14: error: type mismatch;
found : () => Unit
required: Unit => Unit
()=>println("Tom")
^
这是很容易犯的错误,注意到Unit
是类型,而()
是该类型的唯一值,这么写就好像是定义100=>println...
一样荒谬。
实际上应该写成如下形式:
sayHelloBeforeAction{
x:Unit=>println("Tom")
}
Hello!
Tom
然而,此处虽然指定了类型为Unit
的参数x
,但是x
在这里显然多余了,可以使用占位符语法来简化:
sayHelloBeforeAction{
_=>println("Tom")
}
Hello!
Tom
但是话说回来,其实还有提升的空间。注意到在这里没有必要将action
定义为Unit=>Unit
,这么定义使得它必须接受一个类型为Unit
的参数,从而action()
其实被推断为action(())
。因为action
不需要接受任何参数,不妨将其定义为传名参数:
def sayHelloBeforeAction(action: =>Unit){
println("Hello!")
action
}
sayHelloBeforeAction{
println("Tom")
}
Hello!
Tom
注意在定义传名参数action: =>Unit
时,冒号和=>
之间必须有空格。并且在内部使用action
时不能带括号。
网友评论