参数化类型的隐式 class 解析

Implicit class resolution for parameterized types

在下面的示例中,Scala 编译器似乎只识别隐式的 class,当它被定义为采用 Wrapper 的更高种类的表示时。这是为什么?

scala> case class Nested(n: Int)
defined class Nested

scala> case class Wrapper[A <: Product](nested: A)
defined class Wrapper

scala> implicit class I1[W <: Wrapper[A], A <: Product](underlying: W) {
     | def ok1() = true
     | }
defined class I1

scala> Wrapper(Nested(5)).ok1()
<console>:26: error: value ok1 is not a member of Wrapper[Nested]
       Wrapper(Nested(5)).ok1()
                          ^
scala> implicit class I2[W <: Wrapper[_]](underlying: W) {
     | def ok2() = true
     | }
defined class I2

scala> Wrapper(Nested(5)).ok2()
res1: Boolean = true

是否有隐式解析的变通方法,它维护有关嵌套类型的完整信息,允许类型class 证据,例如 TypeTag 附加到它?

注意:上面的示例显示 NestedWrapper 是 class 的情况,但这不是问题的组成部分。这只是为了让控制台会话更短、更简单。

发生这种情况是因为 Scala 的类型推断存在局限性。参见 SI-2272

隐式解析失败,因为编译器无法正确推断 A。如果我们启用 -Xlog-implicits,我们可以看到这一点。请注意 A 被推断为 Nothing:

I1 is not a valid implicit value for Test.w.type => ?{def ok: ?} because:
inferred type arguments [Wrapper[Nested],Nothing] do not conform to method I1's type parameter bounds [W <: Wrapper[A],A <: Product]

如果我们尝试手动实例化 I1,也会发生同样的事情:

scala> val w = Wrapper(Nested(5))
w: Wrapper[Nested] = Wrapper(Nested(5))

scala> new I1(w)
<console>:21: error: inferred type arguments [Wrapper[Nested],Nothing] do not conform to class I1's type parameter bounds [W <: Wrapper[A],A <: Product]
       new I1(w)
       ^
<console>:21: error: type mismatch;
 found   : Wrapper[Nested]
 required: W
       new I1(w)
              ^

现在,work-arounds.

首先,Wrapper是一个案例class,所以不应该成为它有sub-types的理由。您可以删除 W 类型参数,并将 underlying 更改为 Wrapper[A]:

implicit class I1[A <: Product](underlying: Wrapper[A]) {
  def ok = true
}

如果您仍然希望需要两个类型参数,您还可以要求隐式证据表明 W <:< Wrapper[A],同时删除类型参数 W 上的 upper-bound:

implicit class I1[W, A <: Product](underlying: W)(implicit ev: W <:< Wrapper[A]) {
  def ok = true
}

Michael 所说的一切都是真的。这里有一些关于这个问题的额外观点。

由于您编写隐式 class 的方式,您似乎希望隐式 class 适用于 Wrapper 的所有子类型,并具有有关所有相关类型的特定信息尽可能。 (99% 的情况下,扩展 case classes 是个坏主意,但这是可能的,这些技巧也适用于非 case classes)。

技巧基本上是 确保您想要推断的所有类型参数都存在于值参数列表中的某处。要记住的另一件事是:

scala> trait Foo[A]; trait Bar extends Foo[Int]
defined trait Foo
defined trait Bar

scala> implicitly[Bar with Foo[Int] =:= Bar]
res0: =:=[Bar with Foo[Int],Bar] = <function1>

利用这两条知识,你可以像这样重写你的隐式class:

implicit class I1[Y, A <: Product](underlying: Y with Wrapper[A]) {
  def ok1(): (Y, A) = ???
}

并在工作中看到它:

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Nested(n: Int)
case class Wrapper[A <: Product](nested: A)
class Crazy(override val nested: Nested) extends Wrapper[Nested](nested)

implicit class I1[Y, A <: Product](underlying: Y with Wrapper[A]) {
  def ok1(): (Y, A) = ???
}

// Exiting paste mode, now interpreting.


scala> :type Wrapper(Nested(5)).ok1()
(Wrapper[Nested], Nested)

scala> :type new Crazy(Nested(5)).ok1()
(Crazy, Nested)

请注意,Michael 给出的最后一个解决方案基于同一件事:通过将上限移动到隐式参数列表,A 现在出现在值参数列表中,并且可以由编译器推断.