【Scala之旅】隐式参数和隐式转换

本节翻译自

综述:隐式转换和隐式参数是Scala的两个功能强大的工具,在幕后处理很有价值的工作。利用隐式转换和隐式参数,你可以提供优雅地类库,对类库的使用者隐藏那些枯燥乏味的细节。

隐式参数

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

Scala 将寻找的这些参数分为两类:

  • 首先,Scala 将寻找隐式定义和隐式参数,它们可以在带有隐式参数块的方法被调用(不带前缀)时被直接访问。
  • 然后,它会查找隐含在与隐式候选类型关联的所有伴生对象中的成员。

常见问题解答中可以找到有关 Scala 寻找隐含位置的更详细指南

在下面的例子中,我们定义了一个方法 sum,它使用 Monoid 的 addunit 操作来计算一个元素列表的和。请注意,隐式值不能是顶级的。

abstract class SemiGroup[A] {
  def add(x: A, y: A): A
}
abstract class Monoid[A] extends SemiGroup[A] {
  def unit: A
}
object ImplicitTest extends App {
  implicit object StringMonoid extends Monoid[String] {
    def add(x: String, y: String): String = x concat y
    def unit: String = ""
  }
  implicit object IntMonoid extends 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))

  println(sum(List(1, 2, 3)))       // uses IntMonoid implicitly
  println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
}

Monoid 在这里定义了一个名为 add 的操作,它将一对 A 组合起来并返回另一个 A,以及一个称为 unit 的操作,它可以创建一些(特定的)A

为了显示隐式参数如何工作,我们首先分别为字符串和整数定义 Monoid StringMonoidIntMonoidimplicit 关键字指示相应对象可以被隐式地使用。

方法 sum 输入 List[A] 并返回一个 A,它从 unit 中获取最初的 A,并将列表中的每个下一个 A 与具有 add 方法的下一个 A 组合在一起。使参数 m 隐含在这里意味着我们只需要在我们调用方法时提供 xs 参数,如果 Scala 可以找到用于隐式 m 参数的隐含 Monoid[A]

在我们的 main 方法中,我们调用了两次 sum,并只提供了 xs 参数。现在 Scala 会在上面提到的范围中寻找隐含的。第一次调用 sum 时,会为 xs 传递一个 List[Int],这意味着 AInt。 含有 m 的隐含参数列表被省略,因此 Scala 会查找类型为 Monoid[Int] 的隐式参数。 刚刚所讲的第一条规则是

首先,Scala 将寻找隐式定义和隐式参数,它们可以在带有隐式参数块的方法被调用(不带前缀)时被直接访问。

IntMonoid 是一个隐式定义,可以直接在 main 中访问。它也是正确的类型,所以它会自动传递给 sum 方法。

第二次求和传递一个 List[String],这意味着 AString。隐式查找将采用与 Int 相同的方式,但是这次将找到 StringMonoid,并将其自动作为 m 传递。

下面是Scala程序的输出:

6
abc

隐式转换

从类型 ST 类型的隐式转换由函数类型 S => T 的隐式值定义,或者通过隐式方法可转换为这种类型的值。

隐式转换适用于两种情况:

  • 如果表达式 eS 类型,S 不符合表达式的期望类型 T
  • S 类型的 e 的一个选择e.m中,如果选择器 m 并不是 S 里面的成员。

在第一个例子中,一个转换 c 被搜索,它适用于 e,并且其结果类型符合 T。在第二个例子中,一个转换 c 被搜索,它适用于 e,其结果包含一个名为 m 的成员。

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

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

隐式方法 Int => Ordered[Int] 通过 scala.Predef.intWrapper 自动提供。下面提供了一个隐式方法 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.lang.Integer 的 Java 方法时,您可以自由地将它传递给 scala.Int。这是因为 Predef 包含以下隐式转换:

import scala.language.implicitConversions

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

如果在编译隐式转换定义时不加区别地使用编译器警告的话,隐式转换可能会有陷阱。

要关闭这些警告,请采取以下行动:

  • 在隐式转换定义的范围内导入 scala.language.implicitConversions
  • -language:implicitConversions 调用编译器

编译器应用转换时就不会发出警告了。

相关推荐