1. 隐式参数
Scala 中的 implicit 关键字,除了能够加在 class 之前作为一个类的隐式转换之外,见:Scala 进阶(2)—— implicit 用法:隐式转换,
还能够加在参数之前,定义一个隐式参数。
2. 柯里化
隐式参数的应用是基于一个柯里化的函数定义,所以先简单地介绍一下柯里化函数。
简而言之,一个柯里化的函数,允许你拥有多个参数列表。
看下面这个例子:
object CreateProcess { def sendCreate1(req: Request)(re: RequestEngine)(ec: ExecutionContext) = { } def sendCreate2(req: Request, re: RequestEngine, ec: ExecutionContext) = { } }
sendCreate1 就是一个柯里化的函数,于 sendCreate2 相比,它拥有三个参数列表,其本质是当你调用 sendCreate1 这个函数时,进行了三次常规的函数调用,即:
- 第一次调用 sendCreate1(req),返回一个接受入参为 RequestEngine 的函数 XXX1。
- 第二次调用 XXX1(re),返回一个接受入参为 ExecutionContext 的函数 XXX2。
- 第三次调用 XXX2(ec),返回整个函数的返回值,在这里是 Unit。
而隐式参数,提供的是整个最后一组(不是最后一个)柯里化的参数列表:
def sendCreate3(req: Request)(implicit re: RequestEngine, ec: ExecutionContext) = { }
上面这个例子,re 和 ec 都被标记为了 implicit。
3. 隐式参数能做什么?
知道了隐式参数是什么,接下来需要做的就是要知道它能做些什么。
一句话来概括,就是被标记为 implicit 的参数列表可以被隐式地提供(当然也可以显示地提供,就像正常调用一个柯里化的函数)。
我们看一下,如何去调用以上 sendCreate3 这个函数:
object Main { def main(args: Array[String]): Unit = { val req = new Request() val re = new RequestEngine() val ec = ExecutionContext.fromExecutor( new Executor { def execute(runnable: Runnable): Unit = runnable.run() }) CreateProcess.sendCreate3(req)(re, ec) // 显式使用 implicit 参数 } }
这是显示地使用隐式参数,不过既然我们使用了 implicit,一般来说是不推荐这么用的。
然后看一下如何隐式地使用 implicit 参数:
package implicitdemo
object Main { def main(args: Array[String]): Unit = { val req = new Request() CreateProcess.sendCreate3(req) // 隐式使用 implicit 参数
} }
可以发现上面这个例子,没有给 sendCreate3 的最后一个参数列表赋值,
究其原因,是因为在它的作用域中能够找到一个(并且只能是一个,如果有两个或以上,编译器会拒绝寻找隐式参数)类型为 RequestEngine 和 类型为 ExecutionContext 的参数,
并且,这两个参数在声明的时候用了 implicit 修饰:
import java.util.concurrent.Executor import scala.concurrent.ExecutionContext package object implicitdemo { implicit val re = new RequestEngine() implicit val ec = ExecutionContext.fromExecutor( new Executor { def execute(runnable: Runnable): Unit = runnable.run() }) }
4. 类 class 上的隐式参数
声明隐式参数的 implicit 关键字,也可以加在类参数上面,使用的方法和加在函数上一致,显式调用,或在作用域中存在 implicit 修饰的相同类型参数:
class RequestEngine(implicit ec: ExecutionContext) { def send(req: Request) = { Future { // Do something } } }
class RequestEngine { def send(req: Request)(implicit ec: ExecutionContext) = { Future { // Do something } } }
上面两种从功能上说是等价的。
5. 和带缺省参数值的函数对比
Scala 允许定义函数时,带缺省的参数值,例如:
val requestEngine = new RequestEngine() val executionContext = ExecutionContext.fromExecutor( new Executor { def execute(runnable: Runnable): Unit = runnable.run() }) def sendCreate4(req: Request, re: RequestEngine = requestEngine, ec: ExecutionContext = executionContext) = { }
object Main { def main(args: Array[String]): Unit = { val req = new Request() CreateProcess.sendCreate4(req) } }
带缺省参数的函数,和带隐式参数的函数,从功能上来说及其类似,如果说有区别,个人总结了以下几点:
- 隐式参数必须用在柯里化的函数定义中;带缺省值的参数更多用在非柯里化的函数定义中。
- 隐式参数的函数,调用时没有显式或隐式地提供 implicit 参数,会编译错误;带缺省值的参数,没有给则直接使用默认值,所以相对前者更加安全。
- 隐式参数的函数定义使用起来更加灵活,因为它是由调用方决定输入的值;带缺省值的参数的函数相对僵化,在函数定义时就决定了缺省值。