How does the <:< operator work in Scala?

后端 未结 1 1390
南方客
南方客 2021-02-06 11:45

In Scala there\'s a class <:< that witnesses a type constraint. From Predef.scala:

  sealed abstract class <:<[-From, +To]          


        
1条回答
  •  余生分开走
    2021-02-06 12:11

    Suppose we've got the following simple type hierarchy:

    trait Foo
    trait Bar extends Foo
    

    We can ask for proof that Bar extends Foo:

    val ev = implicitly[Bar <:< Foo]
    

    If we run this in a console with -Xprint:typer, we'll see the following:

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

    So the compiler has picked $conforms[Bar] as the implicit value we've asked for. Of course this value has type Bar <:< Bar, but because <:< is covariant in its second type parameter, this is a subtype of Bar <:< Foo, so it fits the bill.

    (There's some magic involved here in the fact that the Scala compiler knows how to find subtypes of the type it's looking for, but it's a fairly generic mechanism and isn't too surprising in its behavior.)

    Now suppose we ask for proof that Bar extends String:

    val ev = implicitly[Bar <:< String]
    

    If you turn on -Xlog-implicits, you'll see this:

    :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]
                              ^
    :9: error: Cannot prove that Bar <:< String.
           val ev = implicitly[Bar <:< String]
                              ^
    

    The compiler tries the Bar <:< Bar again, but since Bar isn't a String, this isn't a subtype of Bar <:< String, so it's not what we need. But $conforms is the only place the compiler can get <:< instances (unless we've defined our own, which would be dangerous), so it quite properly refuses to compile this nonsense.


    To address your second question: the <:<[-From, +To] class is necessary because we need the type parameters for this type class to be useful. The singleton Any <:< Any value could just as well be defined as an object—the decision to use a val and an anonymous class is arguably a little simpler, but it's an implementation detail that you shouldn't ever need to worry about.

    0 讨论(0)
提交回复
热议问题