具有逆差的隐式解决方案

时间:2014-02-20 19:38:46

标签: scala

给出Parent和Child类。

scala> class Parent
defined class Parent

scala> class Child extends Parent
defined class Child

定义父母与子女的隐含

scala> implicit val a = new Parent
a: Parent = Parent@5902f207

scala> implicit val b = new Child
b: Child = Child@3f7d8bac

使用implicitly找出解析的隐式内容。

scala> implicitly[Child] 
res1: Child = Child@3f7d8bac

我的理解说明:

         Parent
           |
         Child -- implicit resolution gets the most specific, lowest sub-type

现在,让我们使用逆变型。

scala> trait A[-T]
defined trait A

scala> case class Concrete[T]() extends A[T]
defined class Concrete

然后定义父类和子类。

scala> class Parent
defined class Parent

scala> class Kid extends Parent
defined class Kid

也为他们创造暗示。

scala> implicit val x = Concrete[Parent]
x: Concrete[Parent] = Concrete()

scala> implicit val y = Concrete[Kid]
y: Concrete[Kid] = Concrete()

scala> implicitly[A[Parent]]
res1: A[Parent] = Concrete()

scala> implicitly[A[Kid]]
    <console>:21: 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]]
                        ^

在第一个示例中(没有逆转),Scala能够解析Child的隐式implicitly[Parent]。在我看来,它正在选择最低的子类型。

但是,使用contravariance时,行为会发生变化。为什么呢?

1 个答案:

答案 0 :(得分:4)

您的隐含类型为Concrete,此处不变。

尝试

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

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

更多的话:

Implicits(值或视图)应该具有显式类型,因此您不会对推断类型感到惊讶。选择隐含的是关于类型的。

它使用与用于选择重载符号替代的重载分辨率转换相同的规则来选择其中一个含义。

对于简单值(不是函数调用),归结为一致性或子类型。

还有一个规则,即“派生类型”(通常是子类)中的定义是首选。

以下是您只能使用常用家用材料进行的测试:

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.<tab>           **

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

编辑:

另一种观点,说明为什么重要的是你正在测试的类型:

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]]
<console>: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]]
<console>: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]]
                        ^

给他们明确的类型,混凝土的方差无关紧要。你总是为你的暗示提供明确的类型,对吧?就像反义词告诉我们的那样?

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)