Scala之旅-隐式参数(IMPLICIT PARAMETERS)和隐式转换(IMPLICIT CONVERSIONS)

隐式参数(IMPLICIT PARAMETERS)

方法可以有一个隐式参数列表,在参数列表的开头用 implicit 关键字标记。如果参数列表中的参数没有像往常那样传递,Scala 将会查看它是否可以获取一个正确类型的隐式值,如果可以,则自动的传递它。

Scala 将寻找参数的位置分为两类:

  • 在带有隐式参数块的方法被调用时,Scala 首先会查找可以被直接获取(没有前缀)的隐式定义和隐式参数。
  • 然后它会查看与隐式候选类型相关联的全部伴生对象(companion objects)中的隐式标记成员。

可以在 FAQ 找到有关 Scala 是如何寻找隐式位置的更详细指南。

在下面的例子中,我们定义了一个方法 sum,它使用 monoidaddunit 计算元素列表的总和。请注意隐式值不能是顶级的(top-level)。

abstract class Monoid[A] {
  def add(x: A, y: A): A
  def unit: A
}

object ImplicitTest {
  implicit val stringMonoid: Monoid[String] = new Monoid[String] {
    def add(x: String, y: String): String = x concat y
    def unit: String = ""
  }

  implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
    def add(x: Int, y: Int): Int = x + y
    def unit: Int = 0
  }

  def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
    if (xs.isEmpty) m.unit
    else m.add(xs.head, sum(xs.tail))

  def main(args: Array[String]): Unit = {
    println(sum(List(1, 2, 3)))       // uses IntMonoid implicitly
    println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
  }
}

这里写图片描述
Nonoid 定义了一个 add 操作,它组合了一对 A 并返回另一个 A,以及一个称为 unit 的操作能够创建一些特定的 A

为了演示隐式参数是如何工作的,我们首先分别为字符串和整数定义了 monoid StringMonoidIntMonoid

关键字 implicit 指明相应的对象可以被隐式地使用。

方法 sum 接受 List[A] 并返回 A,它将从 unit 中取出初始值 A,并将列表中每下一个 A 与方法 add 结合起来 。使参数 m 隐式在这里指在我们在调用方法时只需提供 xs 参数,如果 Scala 可以找到隐式 Monoid[A] 用于隐式参数 m 的话。

在我们的 main 方法中,我们调用两次 sum 方法,并只提供了 xs 参数。现在 Scala 将会在上面提到的范围内查找隐式。第一次调用 sum 方法时传递了一个 List[Int]xs,这意味着 AInt 类型。隐式参数列表 m 被忽略了,因此 Scala 将会查找隐式类型 Monoid[Int]。按照第一查找规则读取:

在带有隐式参数块的方法被调用时,Scala 首先会查找可以被直接获取(没有前缀)的隐式定义和隐式参数。

intMonoid 是一个可以在 main 函数中被直接获取的隐式定义。它也是正确的类型,所以它会自动地传递给 sum 方法。

第二次调用 sum 时 传入了一个 List[String],这意味着 AString 类型。隐式查找的方式与 Int 相同,但是这次将会找到 stringMonoid 方法并自动地将其传入为 m
该程序输出结果为:

6
abc

这里写图片描述

隐式转换(IMPLICIT CONVERSIONS)

一个 S 类型到 T 类型的隐式转换由具有函数类型 S => T 隐式值或由可转化为该类型的隐式方法定义。

隐式转换适用于两种场景:

  • 如果表达式 e 是类型 S,并且 S 不符合表达式的预期类型 T
  • 在选择带有 S 类型 ee.m 中,如果选择器 m 不表示成员 S

在第一种情况下,搜索一个适用于 e 的转换 c 并且其结果要符合类型 T。第二种情况,搜索一个适用于 e 的转换 c 并且其结果类型要包含名为 m 的成员。

如果一个隐式方法 List[A] => Ordered[List[A]] 在范围内,隐式方法 Int => Ordered[Int] 也在,那接下来对两个类型列表 List[Int] 的操作是合法的:

List(1,2,3)  <= List(4,5)

通过 scala.Predef.intWrapper,隐式方法 Int => Ordered[Int] 自动地被提供。下面提供一个隐式方法 List[A] => Ordered[List[A]] 实现的例子。

import scala.language.implicitConversions

implicit def list2ordered[A](x: List[A])
    (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
  new Ordered[List[A]] { 
    //replace with a more useful implementation
    def compare(that: List[A]): Int = 1
  }

这里写图片描述
隐式导入的对象 scala.Predef 声明了几个预定义类型(如 Pair)和方法(如 assert),而且也包含几个隐式转换。

例如,当调用一个 Java 方法期望获得一个 java.lang.Integer 时,你可以使用 scala.Int 代替。这是因为预定义包括了接下来的隐式转换:

import scala.language.implicitConversions

implicit def int2Integer(x: Int) =
  java.lang.Integer.valueOf(x)

因为隐式转换有缺陷,如果不加以区分的使用,编译器将会在编译隐式转换定义时发出警告。

可以采取下面一些方法来关闭警告:

  • 导入 scala.language.implicitConversions 到隐式转换定义的范围
  • 调用编译器时带参数 -language:implicitConversions

这样,当编译器在进行转换时,就不会再发出警告。

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页