Scala 进阶(3)—— implicit 用法:隐式参数

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

带缺省参数的函数,和带隐式参数的函数,从功能上来说及其类似,如果说有区别,个人总结了以下几点:

  1. 隐式参数必须用在柯里化的函数定义中;带缺省值的参数更多用在非柯里化的函数定义中。
  2. 隐式参数的函数,调用时没有显式或隐式地提供 implicit 参数,会编译错误;带缺省值的参数,没有给则直接使用默认值,所以相对前者更加安全。
  3. 隐式参数的函数定义使用起来更加灵活,因为它是由调用方决定输入的值;带缺省值的参数的函数相对僵化,在函数定义时就决定了缺省值。

 

上一篇:System 15-2: Implicit Free Lists (Find a Free Block)


下一篇:TIMESTAMP with implicit DEFAULT value is deprecated