Scala之旅——隐式参数

一个方法可以拥有隐式参数列表,它由_implicit_关键字在参数列表的开头作为标记。如果这个参数列表的参数没有正常传递的话,Scala会去查找一个正确类型的隐式值,如果找到了,则会自动传递。

Scala查找找些参数的位置分以下2种类型:

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

关于Scala查找隐式值的位置,在the FAQ有更加详细的讲解。

下面的例子中我们定义了一个方法sum,通过使用Monidaddunit的操作来计算列表中元素的和。注意下面的隐式值不能在顶层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}
}

这里Monid类定义了一个方法add,用来组合一对A类型的值并且返回一个A类型的值,另外定义了一个unit方法用来创建一个具体的A类型的值。

为了展示隐式参数是如何起作用的,我们首先定义了针对字符串和整数的Monoid,分别是StringMonoidIntMoniod。关键字implicit表明了相关的对象可以被隐式使用。

方法sum接受一个List[A]并且返回一个A,它从unit方法中获取一个A类型的初始值,然后使用add方法,对于列表中的每一个A类型的值进行组合。这里我们让参数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

该程序输出如下

1
2
6
abc
小鹏 wechat
公众号:数据Man
0%