【问题标题】:Implicit class resolution for parameterized types参数化类型的隐式类解析
【发布时间】:2017-02-13 23:28:31
【问题描述】:

在以下示例中,似乎 Scala 编译器仅在将隐式类定义为采用 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

是否存在隐式解析的解决方法,它保留有关嵌套类型的完整信息,允许附加类型类证据,例如 TypeTag?

注意:上面的示例显示 Nested 和 Wrapper 是案例类,但这不是问题的组成部分。这只是一种更短、更简单的控制台会话的便利。

【问题讨论】:

  • I1 对我来说意义不大。 A &lt;: Product 已经存在类型约束,为什么要在隐式转换中复制它?
  • @cchantep 你怎么能隐含知道A 的详细信息或关联,例如TypeTag?

标签: scala higher-kinded-types implicits


【解决方案1】:

这是由于 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)
              ^

现在,解决方法。

首先,Wrapper 是一个 case 类,所以 不应该 它有子类型。可以去掉W类型参数,把underlying改成Wrapper[A]:

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

如果您仍希望需要两个类型参数,您还可以要求隐含证据表明 W &lt;:&lt; Wrapper[A],同时移除类型参数 W 的上限:

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

【讨论】:

  • -Xlog-implicits 是个好建议。
【解决方案2】:

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

由于您编写隐式类的方式,您似乎希望隐式类适用于Wrapper 的所有子类型,并尽可能包含有关所有类型的具体信息。 (99% 的情况下,扩展案例类是个坏主意,但这是可能的,而且这些技巧也适用于非案例类。

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

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>

掌握这两个知识,你可以像这样重写你的隐式类:

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 现在出现在值参数列表中,并且可以由编译器推断。

【讨论】:

  • 附加上下文很有帮助。
猜你喜欢
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
相关资源
最近更新 更多