Implicit Resolution with Contravariance

前端 未结 1 1515

Given classes Parent and Child.

scala> class Parent
defined class Parent

scala> class Child extends Parent
defined class Child

Define im

1条回答
  •  广开言路
    2021-02-15 17:34

    Your implicits are typed Concrete and that is invariant here.

    Try either

    case class Concrete[-T]() extends A[T]
    

    or

    implicit val x: A[Parent] = Concrete[Parent]
    

    More words:

    Implicits (values or views) should have an explicit type so you're never surprised by the inferred type. Picking the implicit is all about the type.

    It picks one of your implicits using the same rules as the overloading resolution conversion that is used to pick alternatives of an overloaded symbol.

    For simple values (not function calls), that comes down to conformance or subtyping.

    There is also the rule that a definition in a "derived type" (usually a subclass) is preferred.

    Here is a test you can do using only commonly available household materials:

    scala> :power
    ** Power User mode enabled - BEEP WHIR GYVE **
    ** :phase has been set to 'typer'.          **
    ** scala.tools.nsc._ has been imported      **
    ** global._, definitions._ also imported    **
    ** Try  :help, :vals, power.           **
    
    scala> trait A[-T]
    defined trait A
    
    scala> case class Concrete[T](i: Int) extends A[T]
    defined class Concrete
    
    scala> class Parent ; class Kid extends Parent
    defined class Parent
    defined class Kid
    
    // it will pick X if X isAsSpecific as Y but not conversely
    scala> typer.infer.isAsSpecific(typeOf[Concrete[Kid]],typeOf[Concrete[Parent]])
    res0: Boolean = false
    
    scala> typer.infer.isAsSpecific(typeOf[Concrete[Parent]],typeOf[Concrete[Kid]])
    res1: Boolean = false
    
    scala> case class Concrete[-T](i: Int) extends A[T]
    defined class Concrete
    
    scala> typer.infer.isAsSpecific(typeOf[Concrete[Kid]],typeOf[Concrete[Parent]])
    res2: Boolean = false
    
    scala> typer.infer.isAsSpecific(typeOf[Concrete[Parent]],typeOf[Concrete[Kid]])
    res3: Boolean = true
    

    Edit:

    Another view of why it matters what type you're testing:

    scala> trait A[-T]
    defined trait A
    
    scala> case class Concrete[T](i: Int) extends A[T]  // invariant
    defined class Concrete
    
    scala> class Parent ; class Kid extends Parent
    defined class Parent
    defined class Kid
    
    scala> implicitly[Concrete[Parent] <:< Concrete[Kid]]
    :13: error: Cannot prove that Concrete[Parent] <:< Concrete[Kid].
                  implicitly[Concrete[Parent] <:< Concrete[Kid]]
                            ^
    
    scala> implicit val x: Concrete[Parent] = Concrete[Parent](3)  // the inferred type
    x: Concrete[Parent] = Concrete(3)
    
    scala> implicit val y  = Concrete[Kid](4)
    y: Concrete[Kid] = Concrete(4)
    
    // both values conform to A[Kid] (because A is contravariant)
    // but when it puts x and y side-by-side to see which is more specific,
    // it no longer cares that you were looking for an A.  All it knows is
    // that the values are Concrete.  The same thing happens when you overload
    // a method; if there are two candidates, it doesn't care what the expected
    // type is at the call site or how many args you passed.
    
    scala> implicitly[A[Kid]]
    :15: error: ambiguous implicit values:
     both value x of type => Concrete[Parent]
     and value y of type => Concrete[Kid]
     match expected type A[Kid]
                  implicitly[A[Kid]]
                            ^
    

    Give them explicit types and the variance of Concrete won't matter. You always supply explicit types for your implicits, right? Just like retronym tells us to?

    scala> implicit val x: A[Parent] = Concrete[Parent](3)
    x: A[Parent] = Concrete(3)
    
    scala> implicit val y: A[Kid] = Concrete[Kid](4)
    y: A[Kid] = Concrete(4)
    
    scala> implicitly[A[Kid]]
    res2: A[Kid] = Concrete(3)
    

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