隐式

隐式修饰符

LocalModifier  ::= ‘implicit’
ParamClauses   ::= {ParamClause} [nl] ‘(’ ‘implicit’ Params ‘)’

implicit 修饰符标记的模板成员和参数可以传递给 隐式参数,并且可以用作称为 视图 的隐式转换。implicit 修饰符对于所有类型成员以及 顶层对象 都是非法的。

示例 Monoid

以下代码定义了一个 Monoid 的抽象类和两个具体的实现,StringMonoidIntMonoid。这两个实现被标记为隐式。

abstract class Monoid[A] extends SemiGroup[A] {
  def unit: A
  def add(x: A, y: A): A
}
object Monoids {
  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
  }
}

隐式参数

方法的隐式参数列表 (implicit ´p_1´,...,´p_n´) 将参数 ´p_1, ..., p_n´ 标记为隐式。方法或构造函数只能有一个隐式参数列表,并且它必须是给定的最后一个参数列表。

具有隐式参数的方法可以像普通方法一样应用于参数。在这种情况下,implicit 标签没有效果。但是,如果这样的方法缺少其隐式参数的参数,则会自动提供这些参数。

可以传递给类型为 ´T´ 的隐式参数的实际参数分为两类。首先,所有在方法调用点可以访问且没有前缀的标识符 ´x´ 都是合格的,并且表示 隐式定义 或隐式参数。为了在没有前缀的情况下访问,标识符必须是局部名称、封闭模板的成员或由 导入子句 引入的名称。如果根据此规则没有合格的标识符,那么其次,属于隐式参数类型 ´T´ 的隐式作用域的某些对象的 implicit 成员也是合格的。

类型 ´T´ 的隐式作用域 包含与隐式参数类型相关联的所有类的 伴生模块。在这里,我们说一个类 ´C´ 与类型 ´T´ 相关联,如果它是 ´T´ 的某些部分的 基类

类型 ´T´ 的部分

请注意,包在内部表示为具有伴生模块的类,以保存包成员。因此,在包对象中定义的隐式值是包前缀类型的隐式范围的一部分。

如果有多个符合隐式参数类型的合格参数,则将使用静态 重载解析 规则选择最具体的参数。如果参数具有默认参数,并且找不到隐式参数,则使用默认参数。

示例

假设来自 Monoid 示例 的类,以下是一个使用 monoid 的 addunit 操作计算元素列表的总和的方法。

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

所讨论的 monoid 被标记为隐式参数,因此可以根据列表的类型推断出来。例如,考虑在 stringMonoidintMonoid 可见的情况下调用 sum(List(1, 2, 3))。我们知道 sum 的形式类型参数 a 需要实例化为 Int。唯一符合隐式形式参数类型 Monoid[Int] 的合格对象是 intMonoid,因此该对象将作为隐式参数传递。

此讨论还表明,在任何类型参数 推断 之后,隐式参数才会被推断。

隐式方法本身可以具有隐式参数。以下来自模块 scala.List 的方法就是一个例子,该方法将列表注入 scala.Ordered 类,前提是列表的元素类型也可以转换为该类型。

implicit def list2ordered[A](x: List[A])
  (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
  ...

另外假设一个方法

implicit def int2ordered(x: Int): Ordered[Int]

将整数注入 Ordered 类。现在我们可以定义一个在有序列表上的 sort 方法

def sort[A](xs: List[A])(implicit a2ordered: A => Ordered[A]) = ...

我们可以将 sort 应用于整数列表的列表 yss: List[List[Int]],如下所示

sort(yss)

上面的调用将通过传递两个嵌套的隐式参数来完成

sort(yss)((xs: List[Int]) => list2ordered[Int](xs)(int2ordered))

传递给隐式参数的隐式参数的可能性引发了无限递归的可能性。例如,有人可能会尝试定义以下方法,该方法将所有类型注入 Ordered

implicit def magic[A](x: A)(implicit a2ordered: A => Ordered[A]): Ordered[A] =
  a2ordered(x)

现在,如果有人试图将sort应用于一个类型没有注入Ordered类的参数arg,那么就会得到一个无限扩展。

sort(arg)(x => magic(x)(x => magic(x)(x => ... )))

这种无限扩展应该被检测到并报告为错误,但是为了支持递归值的故意隐式构造,我们允许隐式参数被标记为按名称传递。在调用站点,如果隐式值的递归使用发生在按名称传递的隐式参数中,则允许这样做。

考虑以下示例:

trait Foo {
  def next: Foo
}

object Foo {
  implicit def foo(implicit rec: Foo): Foo =
    new Foo { def next = rec }
}

val foo = implicitly[Foo]
assert(foo eq foo.next)

与上面的magic情况一样,由于方法foo的递归隐式参数rec,它会发散。如果我们将隐式参数标记为按名称传递:

trait Foo {
  def next: Foo
}

object Foo {
  implicit def foo(implicit rec: => Foo): Foo =
    new Foo { def next = rec }
}

val foo = implicitly[Foo]
assert(foo eq foo.next)

该示例将编译,断言成功。

编译时,这种递归按名称传递的隐式参数会在调用站点被提取为局部合成对象的val成员,如下所示:

val foo: Foo = scala.Predef.implicitly[Foo](
  {
    object LazyDefns$1 {
      val rec$1: Foo = Foo.foo(rec$1)
                       //      ^^^^^
                       // recursive knot tied here
    }
    LazyDefns$1.rec$1
  }
)
assert(foo eq foo.next)

请注意,rec$1的递归使用发生在foo的按名称传递参数中,因此被延迟。反糖化与程序员显式构造这种递归值的方式相匹配。

为了防止无限扩展,例如上面的magic示例,编译器会跟踪一个“开放隐式类型”堆栈,用于当前正在搜索隐式参数。每当搜索类型T的隐式参数时,T都会被添加到堆栈中,并与生成它的隐式定义配对,以及它是否需要满足按名称传递的隐式参数。一旦隐式参数的搜索明确失败或成功,该类型就会从堆栈中移除。每次要将类型添加到堆栈之前,都会检查它是否与由相同隐式定义生成的现有条目等效,然后:

这里,´T´的核心类型是´T´,其中别名已扩展,顶级类型注释细化已删除,并且顶级存在绑定变量的出现已替换为其上限。

如果´T´与´U´等效,或者´T´和´U´的顶级类型构造函数具有共同元素并且´T´比´U´更复杂,并且´T´和´U´的覆盖集相等,则核心类型´T´支配类型´U´。

类型´T´的顶级类型构造函数集´\mathit{ttcs}(T)´取决于类型的形式

核心类型的复杂度´\operatorname{complexity}(T)´是一个整数,它也取决于类型的形式

类型´T´的覆盖集´\mathit{cs}(T)´是类型中提到的类型指示符集。例如,给定以下内容,

type A = List[(Int, Int)]
type B = List[(Int, (Int, Int))]
type C = List[(Int, String)]

相应的覆盖集是

示例

当为某个类型为 List[List[List[Int]]] 的列表 xs 键入 sort(xs) 时,搜索隐式参数的类型序列为

List[List[Int]] => Ordered[List[List[Int]]],
List[Int] => Ordered[List[Int]],
Int => Ordered[Int]

所有类型都共享公共类型构造函数 scala.Function1,但每个新类型的复杂度都低于前一个类型的复杂度。因此,代码类型检查通过。

示例

ys 为某个类型列表,该类型无法转换为 Ordered。例如

val ys = List(new IllegalArgumentException, new ClassCastException, new Error)

假设上述 magic 的定义在作用域内。那么搜索隐式参数的类型序列为

Throwable => Ordered[Throwable],
Throwable => Ordered[Throwable],
...

由于序列中的第二个类型等于第一个类型,编译器将发出错误信号,表示发散的隐式扩展。

视图

隐式参数和方法还可以定义称为视图的隐式转换。从类型 ´S´ 到类型 ´T´ 的视图 由具有函数类型 ´S´ => ´T´(=> ´S´) => ´T´ 的隐式值定义,或者由可转换为该类型值的某个方法定义。

视图在三种情况下应用

  1. 如果表达式 ´e´ 的类型为 ´T´,并且 ´T´ 不符合表达式的预期类型 ´\mathit{pt}´。在这种情况下,将搜索一个适用于 ´e´ 且其结果类型符合 ´\mathit{pt}´ 的隐式 ´v´。搜索过程与隐式参数的情况相同,其中隐式作用域是 ´T´ => ´\mathit{pt}´ 的作用域。如果找到这样的视图,则表达式 ´e´ 将转换为 ´v´(´e´)
  2. 在选择 ´e.m´ 中,其中 ´e´ 的类型为 ´T´,如果选择器 ´m´ 不表示 ´T´ 的可访问成员。在这种情况下,将搜索一个适用于 ´e´ 且其结果包含名为 ´m´ 的成员的视图 ´v´。搜索过程与隐式参数的情况相同,其中隐式作用域是 ´T´ 的作用域。如果找到这样的视图,则选择 ´e.m´ 将转换为 ´v´(´e´).´m´
  3. 在选择 ´e.m(\mathit{args})´ 中,其中 ´e´ 的类型为 ´T´,如果选择器 ´m´ 表示 ´T´ 的某些成员,但这些成员中没有一个适用于参数 ´\mathit{args}´。在这种情况下,将搜索一个适用于 ´e´ 且其结果包含适用于 ´\mathit{args}´ 的方法 ´m´ 的视图 ´v´。搜索过程与隐式参数的情况相同,其中隐式作用域是 ´T´ => ´\mathit{pt}´ 的作用域,其中 ´\mathit{pt}´ 是结构类型 ´{ def m(\mathit{args}: T_1 , ... , T_n): U }´。如果找到这样的视图,则选择 ´e.m´ 将转换为 ´v´(´e´).´m(\mathit{args})´

如果找到隐式视图,它可以按值调用或按名称调用方式接受其参数 ´e´。但是,按值调用的隐式优先于按名称调用的隐式。

对于隐式参数,如果存在多个可能的候选者(无论是按值调用还是按名称调用类别),则应用重载解析。

示例排序

scala.Ordered[A]包含一个方法

  def <= [B >: A](that: B)(implicit b2ordered: B => Ordered[B]): Boolean

假设两个类型为List[Int]的列表xsys,并假设在此处定义的list2orderedint2ordered方法在范围内。那么操作

  xs <= ys

是合法的,并且扩展为

  list2ordered(xs)(int2ordered).<=
    (ys)
    (xs => list2ordered(xs)(int2ordered))

list2ordered的第一次应用将列表xs转换为Ordered类的实例,而第二次出现是传递给<=方法的隐式参数的一部分。

上下文边界和视图边界

  TypeParam ::= (id | ‘_’) [TypeParamClause] [‘>:’ Type] [‘<:’ Type]
                {‘<%’ Type} {‘:’ Type}

方法或非特质类的类型参数´A´可以有一个或多个视图边界´A´ <% ´T´。在这种情况下,类型参数可以实例化为任何类型´S´,该类型可以通过应用视图转换为边界´T´。

方法或非特质类的类型参数´A´也可以有一个或多个上下文边界´A´ : ´T´。在这种情况下,类型参数可以实例化为任何类型´S´,只要在实例化点存在证据证明´S´满足边界´T´。此类证据包括具有类型´T[S]´的隐式值。

包含具有视图或上下文边界的类型参数的方法或类被视为等效于具有隐式参数的方法。首先考虑具有单个参数的视图和/或上下文边界的情况,例如

def ´f´[´A´ <% ´T_1´ ... <% ´T_m´ : ´U_1´ : ´U_n´](´\mathit{ps}´): ´R´ = ...

那么上面的方法定义扩展为

def ´f´[´A´](´\mathit{ps}´)(implicit ´v_1´: ´A´ => ´T_1´, ..., ´v_m´: ´A´ => ´T_m´,
                       ´w_1´: ´U_1´[´A´], ..., ´w_n´: ´U_n´[´A´]): ´R´ = ...

其中´v_i´和´w_j´是为新引入的隐式参数提供的新的名称。这些参数称为证据参数

如果类或方法具有多个视图或上下文边界类型参数,则每个此类类型参数将按其出现的顺序扩展为证据参数,并且所有生成的证据参数将连接在一个隐式参数部分中。由于特质不接受构造函数参数,因此此转换不适用于它们。因此,特质中的类型参数不能是视图或上下文边界。

如果存在隐式参数部分,证据参数将被追加到现有的隐式参数部分。

例如

def foo[A: M](implicit b: B): C
// expands to:
// def foo[A](implicit evidence´1: M[A], b: B): C
示例

来自 Ordered 示例<= 方法可以更简洁地声明如下

def <= [B >: A <% Ordered[B]](that: B): Boolean

清单

清单是类型描述符,可以由 Scala 编译器自动生成,作为隐式参数的参数。Scala 标准库包含一个包含四个清单类的层次结构,OptManifest 位于顶层。它们的签名遵循以下概述。

trait OptManifest[+T]
object NoManifest extends OptManifest[Nothing]
trait ClassManifest[T] extends OptManifest[T]
trait Manifest[T] extends ClassManifest[T]

如果方法或构造函数的隐式参数是类 OptManifest[T] 的子类型 ´M[T]´,将根据以下规则确定 ´M[S]´ 的清单

首先,如果已经存在与 ´M[T]´ 匹配的隐式参数,则选择该参数。

否则,如果 ´M´ 是特征 Manifest,则让 ´\mathit{Mobj}´ 为伴生对象 scala.reflect.Manifest,否则为伴生对象 scala.reflect.ClassManifest。如果 ´M´ 是特征 Manifest,则让 ´M'´ 为特征 Manifest,否则为特征 OptManifest。然后应用以下规则。

  1. 如果 ´T´ 是值类或以下类之一:AnyAnyValObjectNullNothing,则通过选择相应的清单值 Manifest.´T´(存在于 Manifest 模块中)来生成其清单。
  2. 如果 ´T´ 是 Array[´S´] 的实例,则使用调用 ´\mathit{Mobj}´.arrayType[S](m) 生成清单,其中 ´m´ 是为 ´M[S]´ 确定的清单。
  3. 如果 ´T´ 是其他类类型 ´S´#´C[U_1, ..., U_n]´,其中前缀类型 ´S´ 无法从类 ´C´ 静态确定,则使用调用 ´\mathit{Mobj}´.classType[T](´m_0´, classOf[T], ´ms´) 生成清单,其中 ´m_0´ 是为 ´M'[S]´ 确定的清单,´ms´ 是为 ´M'[U_1], ..., M'[U_n]´ 确定的清单。
  4. 如果 ´T´ 是其他类类型,具有类型参数 ´U_1, ..., U_n´,则使用调用 ´\mathit{Mobj}´.classType[T](classOf[T], ´ms´) 生成清单,其中 ´ms´ 是为 ´M'[U_1], ..., M'[U_n]´ 确定的清单。
  5. 如果 ´T´ 是单例类型 ´p´.type,则使用调用 ´\mathit{Mobj}´.singleType[T](´p´) 生成清单。
  6. 如果 ´T´ 是细化类型 ´T' { R }´,则为 ´T'´ 生成清单。(也就是说,细化永远不会反映在清单中)。
  7. 如果 `T` 是一个交集类型 `T_1` 与 ... 与 `T_n`,其中 `n > 1`,则结果取决于是否需要确定完整的清单。如果 `M` 是特质 `Manifest`,则使用调用 `Manifest.intersectionType[T](ms)` 生成清单,其中 `ms` 是为 `M[T_1], ..., M[T_n]` 确定的清单。否则,如果 `M` 是特质 `ClassManifest`,则为类型 `T_1, ..., T_n` 的 交集支配者 生成清单。
  8. 如果 `T` 是其他类型,则如果 `M` 是特质 `OptManifest`,则从指示符 `scala.reflect.NoManifest` 生成清单。如果 `M` 是与 `OptManifest` 不同的类型,则会导致静态错误。