呼叫站点
让我们看看类型推断器在你写的时候做了什么:
f(new X())
首先要弄清楚f的模板参数A是什么。 Scala 中的类型推断在参数列表中从左到右进行,因此很简单(假设 new X 的类型为 X),我们得到
f[X](new X)
现在编译器需要找到 X <:< X 类型的隐式值(请记住,A 已解析为 X)。
为了查找隐式值,编译器会在不同的地方查找,其中包括您当前的范围(在其中导入了Predef._)。
然后编译器找到Predef.$conforms:
implicit def $conforms[A]: A <:< A = // some implementation
所以这可以用来产生一个X <:< X,通过使用X作为参数来调用它:
f[X](new X)(Predef.$conforms[X])
$conforms 的实际实现与类型检查器无关。
方法实现
现在让我们看看实现:
def f[A](value: A)(implicit ev: A <:< X) = { value.foo }
值的类型为A(所以有些未知)。您想在value 上致电foo。由于foo 未在A 上定义,因此编译器正在寻找将A 转换为具有foo 的内容的隐式函数(或方法)。
范围内有这样一个东西:ev (A <:< B extends A => B)。
因此,编译器使用ev 插入隐式转换:
ev(value).foo
关于方差的小提示
您可能已经注意到,<:< 的参数变体:<:<[-From, +To]。这可用于生成实际的子类型证据。考虑:
class A
class B extends A
val ev1: A <:< A = conforms
val ev2: B <:< A = ev1 // makes sense, works because of variance
// Also
val ev3: B <:< B = conforms
val ev4: B <:< A = ev3 // makes sense, works because of variance
这就是为什么不需要带有两个类型参数的conforms 方法的明显原因。此外,请注意,=:= 特别不需要这种行为(因为这是类型等价的),所以它是不变的。