本节翻译自
综述:隐式转换和隐式参数是Scala的两个功能强大的工具,在幕后处理很有价值的工作。利用隐式转换和隐式参数,你可以提供优雅地类库,对类库的使用者隐藏那些枯燥乏味的细节。
隐式参数
一个方法可以有一个隐式 参数列表,在参数列表的开始处由 implicit 关键字标记。如果参数列表中的参数没有像往常那样传递,Scala 会查看它是否可以获得正确类型的隐式值,如果可以,则自动传递它。
Scala 将寻找的这些参数分为两类:
- 首先,Scala 将寻找隐式定义和隐式参数,它们可以在带有隐式参数块的方法被调用(不带前缀)时被直接访问。
- 然后,它会查找隐含在与隐式候选类型关联的所有伴生对象中的成员。
在常见问题解答中可以找到有关 Scala 寻找隐含位置的更详细指南
在下面的例子中,我们定义了一个方法 sum
,它使用 Monoid 的 add
和 unit
操作来计算一个元素列表的和。请注意,隐式值不能是顶级的。
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 StringMonoid
和 IntMonoid
。implicit
关键字指示相应对象可以被隐式地使用。
方法 sum
输入 List[A]
并返回一个 A
,它从 unit
中获取最初的 A
,并将列表中的每个下一个 A
与具有 add
方法的下一个 A
组合在一起。使参数 m
隐含在这里意味着我们只需要在我们调用方法时提供 xs
参数,如果 Scala 可以找到用于隐式 m
参数的隐含 Monoid[A]
。
在我们的 main
方法中,我们调用了两次 sum
,并只提供了 xs
参数。现在 Scala 会在上面提到的范围中寻找隐含的。第一次调用 sum
时,会为 xs
传递一个 List[Int]
,这意味着 A
是 Int
。 含有 m
的隐含参数列表被省略,因此 Scala 会查找类型为 Monoid[Int]
的隐式参数。 刚刚所讲的第一条规则是
首先,Scala 将寻找隐式定义和隐式参数,它们可以在带有隐式参数块的方法被调用(不带前缀)时被直接访问。
intMonoid
是一个隐式定义,可以直接在 main
中访问。它也是正确的类型,所以它会自动传递给 sum
方法。
第二次求和传递一个 List[String]
,这意味着 A
是 String
。隐式查找将采用与 Int
相同的方式,但是这次将找到 stringMonoid
,并将其自动作为 m
传递。
下面是Scala程序的输出:
6
abc
隐式转换
从类型 S
到 T
类型的隐式转换由函数类型 S => T
的隐式值定义,或者通过隐式方法可转换为这种类型的值。
隐式转换适用于两种情况:
- 如果表达式
e
是S
类型,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
调用编译器
编译器应用转换时就不会发出警告了。