参数化类型的隐式 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
附加到它?
注意:上面的示例显示 Nested
和 Wrapper
是 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
现在出现在值参数列表中,并且可以由编译器推断.
在下面的示例中,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
附加到它?
注意:上面的示例显示 Nested
和 Wrapper
是 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
现在出现在值参数列表中,并且可以由编译器推断.