<:< 运算符在 Scala 中如何工作?

How does the <:< operator work in Scala?

在 Scala 中有一个 class <:< 见证类型约束。来自 Predef.scala:

  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

TraversableOncetoMap 方法中有一个使用示例:

def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] =

我不明白的是这是如何工作的。我知道 A <:< B 在语法上等同于类型 <:<[A, B]。但我不明白当且仅当 A <: B 时编译器如何找到该类型的隐式。我假设 $conforms 定义中的 asInstanceOf 调用以某种方式使这成为可能,但是如何实现呢?此外,使用抽象 class 的单例实例而不是仅使用 object 是否重要?

假设我们有以下简单的类型层次结构:

trait Foo
trait Bar extends Foo

我们可以要求证明 Bar 扩展了 Foo:

val ev = implicitly[Bar <:< Foo]

如果我们 运行 在带有 -Xprint:typer 的控制台中执行此操作,我们将看到以下内容:

private[this] val ev: <:<[Bar,Foo] =
  scala.this.Predef.implicitly[<:<[Bar,Foo]](scala.this.Predef.$conforms[Bar]);

所以编译器选择 $conforms[Bar] 作为我们要求的隐式值。当然这个值的类型是 Bar <:< Bar,但是因为 <:< 在它的第二个类型参数中是协变的,这是 Bar <:< Foo 的子类型,所以它符合要求。

(Scala 编译器知道如何找到它正在寻找的类型的子类型这一事实涉及一些魔法,但它是一种相当通用的机制并且其行为并不令人惊讶。)

现在假设我们要求证明 Bar 扩展了 String:

val ev = implicitly[Bar <:< String]

如果您打开 -Xlog-implicits,您将看到:

<console>:9: $conforms is not a valid implicit value for <:<[Bar,String] because:
hasMatchingSymbol reported error: type mismatch;
 found   : <:<[Bar,Bar]
 required: <:<[Bar,String]
       val ev = implicitly[Bar <:< String]
                          ^
<console>:9: error: Cannot prove that Bar <:< String.
       val ev = implicitly[Bar <:< String]
                          ^

编译器再次尝试 Bar <:< Bar,但由于 Bar 不是 String,这不是 Bar <:< String 的子类型,所以它不是什么我们需要。但是 $conforms 是编译器唯一可以获得 <:< 实例的地方(除非我们已经定义了自己的实例,那样会很危险),所以它拒绝编译这个废话是完全正确的。


要解决您的第二个问题:<:<[-From, +To] class 是必需的,因为我们需要此类型 class 的类型参数才有用。单例 Any <:< Any 值也可以定义为一个对象——使用 val 和匿名 class 的决定可以说更简单一些,但这是一个您应该了解的实现细节再也不用担心了。